>
+ */
+ public array $decorators = [];
+}
diff --git a/app/Controllers/Admin/AdminController.php b/app/Controllers/Admin/AdminController.php
new file mode 100644
index 0000000..89bf8aa
--- /dev/null
+++ b/app/Controllers/Admin/AdminController.php
@@ -0,0 +1,89 @@
+layout = "admin";
+ $this->uri_path = "admin/";
+ $this->view_path = "admin/";
+ }
+
+ //생성
+ protected function create_process_result($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message, DEFAULTS['STATUS']);
+ return parent::create_process_result($message);
+ }
+ protected function create_process_failed($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message);
+ return parent::create_process_failed($message);
+ }
+ //수정
+ protected function modify_process_result($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message, DEFAULTS['STATUS']);
+ return parent::modify_process_result($message);
+ }
+ protected function modify_process_failed($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message);
+ return parent::modify_process_failed($message);
+ }
+ //단일필드작업
+ protected function toggle_process_result($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message, DEFAULTS['STATUS']);
+ return parent::toggle_process_result($message);
+ }
+ protected function toggle_process_failed($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message);
+ return parent::toggle_process_failed($message);
+ }
+ //일괄처리작업
+ protected function batchjob_process_result($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message, DEFAULTS['STATUS']);
+ return parent::batchjob_process_result($message);
+ }
+ protected function batchjob_process_failed($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message);
+ return parent::batchjob_process_failed($message);
+ }
+ //삭제 delete,batchjob_delete 공통사용
+ protected function delete_process_result($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message, DEFAULTS['STATUS']);
+ return parent::delete_process_result($message);
+ }
+ protected function delete_process_failed($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message);
+ return parent::delete_process_failed($message);
+ }
+ //일괄삭제
+ protected function batchjob_delete_process_result($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message, DEFAULTS['STATUS']);
+ return parent::batchjob_delete_process_result($message);
+ }
+ protected function batchjob_delete_process_failed($message): RedirectResponse|string
+ {
+ MyLogService::save($this->getService(), __FUNCTION__, $this->myauth, $message);
+ return parent::batchjob_delete_process_failed($message);
+ }
+}
diff --git a/app/Controllers/Admin/Home.php b/app/Controllers/Admin/Home.php
new file mode 100644
index 0000000..781cc4c
--- /dev/null
+++ b/app/Controllers/Admin/Home.php
@@ -0,0 +1,35 @@
+layout = "admin";
+ $this->uri_path = "admin/";
+ $this->view_path = "admin/";
+ $this->title = "관리자페이지 메인";
+ $this->helper = new CommonHelper();
+ }
+ protected function getService(): UserService
+ {
+ if ($this->service === null) {
+ $this->service = new UserService();
+ }
+ return $this->service;
+ }
+ public function index(): string
+ {
+ helper(['form']);
+ return view('admin/welcome_message', ['viewDatas' => $this->getViewDatas()]);
+ }
+}
diff --git a/app/Controllers/Admin/MyLogController.php b/app/Controllers/Admin/MyLogController.php
new file mode 100644
index 0000000..1d6afb1
--- /dev/null
+++ b/app/Controllers/Admin/MyLogController.php
@@ -0,0 +1,60 @@
+title = lang("MyLog.title");
+ $this->helper = new MyLogHelper();
+ }
+ protected function getService(): MyLogService
+ {
+ if ($this->service === null) {
+ $this->service = new MyLogService();
+ $this->class_name = "MyLog";
+ $this->class_path = $this->class_name;
+ }
+ return $this->service;
+ }
+ public function getUserModel(): UserModel
+ {
+ if ($this->_userModel === null) {
+ $this->_userModel = new UserModel();
+ }
+ return $this->_userModel;
+ }
+ protected function getFormFieldOption(string $field, array $options = []): array
+ {
+ switch ($field) {
+ case 'user_uid':
+ // $this->getUserModel()->where('status', DEFAULTS['STATUS']);
+ $options[$field] = $this->getUserModel()->getFormFieldOption($field);
+ // echo $this->getUserModel()->getLastQuery();
+ // dd($options);
+ break;
+ default:
+ $options = parent::getFormFieldOption($field, $options);
+ break;
+ }
+ return $options;
+ }
+ //View관련
+ protected function view_init(string $action, $fields = []): void
+ {
+ $fields = [
+ 'fields' => ['user_uid', 'class_name', 'method_name', $this->getService()->getModel()::TITLE, 'created_at', 'status', 'content'],
+ ];
+ parent::view_init($action, $fields);
+ }
+}
diff --git a/app/Controllers/Admin/UserController.php b/app/Controllers/Admin/UserController.php
new file mode 100644
index 0000000..0eedf2a
--- /dev/null
+++ b/app/Controllers/Admin/UserController.php
@@ -0,0 +1,59 @@
+title = lang("{$this->getService()->getClassPath()}.title");
+ $this->helper = new UserHelper();
+ }
+ protected function getService(): UserService
+ {
+ if ($this->service === null) {
+ $this->service = new UserService();
+ $this->class_name = $this->service->getClassName();
+ $this->class_path = $this->service->getClassPath();
+ }
+ return $this->service;
+ }
+ protected function setValidation(string $action, string $field, Validation $validation): Validation
+ {
+ switch ($field) {
+ case 'role':
+ //아래 Rule Array는 필드명.* checkbox를 사용
+ $validation->setRule("{$field}.*", $field, $this->getService()->getModel()->getFieldRule($action, $field));
+ break;
+ default:
+ $validation = parent::setValidation($action, $field, $validation);
+ break;
+ }
+ return $validation;
+ }
+ //생성
+ protected function create_init(string $action, $fields = []): void
+ {
+ $fields = [
+ 'fields' => ['id', 'passwd', 'confirmpassword', $this->getService()->getModel()::TITLE, 'email', 'mobile', 'role'],
+ ];
+ parent::create_init($action, fields: $fields);
+ }
+ //수정
+ protected function modify_init(string $action, $fields = []): void
+ {
+ $fields = [
+ 'fields' => ['id', 'passwd', 'confirmpassword', $this->getService()->getModel()::TITLE, 'email', 'mobile', 'role', 'status'],
+ ];
+ parent::modify_init($action, $fields);
+ }
+}
diff --git a/app/Controllers/BaseController.php b/app/Controllers/BaseController.php
new file mode 100644
index 0000000..8b435da
--- /dev/null
+++ b/app/Controllers/BaseController.php
@@ -0,0 +1,58 @@
+
+ */
+ protected $helpers = [];
+
+ /**
+ * Be sure to declare properties for any property fetch you initialized.
+ * The creation of dynamic property is deprecated in PHP 8.2.
+ */
+ // protected $session;
+
+ /**
+ * @return void
+ */
+ public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
+ {
+ // Do Not Edit This Line
+ parent::initController($request, $response, $logger);
+
+ // Preload any models, libraries, etc, here.
+
+ // E.g.: $this->session = \Config\Services::session();
+ }
+}
diff --git a/app/Controllers/CommonController.php b/app/Controllers/CommonController.php
new file mode 100644
index 0000000..24a7b52
--- /dev/null
+++ b/app/Controllers/CommonController.php
@@ -0,0 +1,607 @@
+myauth = service('myauth');
+ }
+ final public function __get($name)
+ {
+ if (!array_key_exists($name, $this->_viewDatas)) {
+ return null;
+ }
+ return $this->_viewDatas[$name];
+ }
+ final public function __set($name, $value): void
+ {
+ $this->_viewDatas[$name] = $value;
+ }
+ final public function getViewDatas(): array
+ {
+ return $this->_viewDatas;
+ }
+
+ protected function setValidation(string $action, string $field, Validation $validation): Validation
+ {
+ switch ($field) {
+ default:
+ $validation->setRule($field, $field, $this->getService()->getModel()->getFieldRule($action, $field));
+ break;
+ }
+ return $validation;
+ }
+ final protected function getValidation(string $action, array $fields): Validation
+ {
+ $validation = service('validation');
+ foreach ($fields as $field) {
+ $validation = $this->setValidation($action, $field, $validation);
+ }
+ return $validation;
+ }
+ //Field별 Form Option용
+ protected function getFormFieldOption(string $field, array $options): array
+ {
+ switch ($field) {
+ default:
+ $options[$field] = lang($this->getService()->getClassPath() . '.' . strtoupper($field));
+ // dd($options);
+ break;
+ }
+ // dd($options);
+ return $options;
+ }
+ final protected function getFormFieldOptions(array $fields, array $options = []): array
+ {
+ foreach ($fields as $field) {
+ if (is_array($field)) {
+ throw new \Exception(__FUNCTION__ . "에서 field array 입니다.\n" . var_export($field, true));
+ }
+ $options = $this->getFormFieldOption($field, $options);
+ }
+ // dd($options);
+ return $options;
+ }
+
+ //Field관련
+ protected function init(string $action, array $fields = []): void
+ {
+ $this->action = $action;
+ $this->fields = array_key_exists('fields', $fields) && is_array($fields['fields']) && count($fields['fields']) ? $fields['fields'] : $this->getService()->getFields();
+ $this->field_rules = array_key_exists('field_rules', $fields) && is_array($fields['field_rules']) && count($fields['field_rules']) ? $fields['field_rules'] : $this->getService()->getFieldRules($this->action, $this->fields);
+ $this->filter_fields = array_key_exists('filter_fields', $fields) && is_array($fields['filter_fields']) && count($fields['filter_fields']) ? $fields['filter_fields'] : $this->getService()->getFilterFields();
+ $this->field_options = array_key_exists('field_options', $fields) && is_array($fields['field_optionss']) && count($fields['field_options']) ? $fields['field_options'] : $this->getFormFieldOptions($this->filter_fields);
+ $this->batchjob_fields = array_key_exists('batchjob_fields', $fields) && is_array($fields['batchjob_fields']) && count($fields['batchjob_fields']) ? $fields['filter_fields'] : $this->getService()->getBatchJobFields();
+ }
+ // 생성
+ protected function create_init(string $action, $fields = []): void
+ {
+ $this->init($action, $fields);
+ }
+ protected function create_form_process(): void {}
+ final public function create_form(): RedirectResponse|string
+ {
+ $this->create_init('create');
+ return $this->create_form_procedure();
+ }
+ protected function create_form_process_result(): string
+ {
+ return view(
+ $this->view_path . "create",
+ data: ['viewDatas' => $this->getViewDatas()]
+ );
+ }
+ final protected function create_form_procedure(): RedirectResponse|string
+ {
+ try {
+ helper(['form']);
+ $this->create_form_process();
+ $this->forms = ['attributes' => ['method' => "post",], 'hiddens' => []];
+ return $this->create_form_process_result();
+ } catch (\Exception $e) {
+ return redirect()->back()->with('error', $e->getMessage());
+ }
+ }
+ protected function create_validate(string $action, array $fields): array
+ {
+ //변경할 값 확인 : Upload된 파일 검증시 $this->request->getPOST()보다 먼처 체크필요
+ $validation = $this->getValidation($action, $fields);
+ if (!$validation->withRequest($this->request)->run()) {
+ throw new \Exception("{$this->getService()->getClassName()} 작업 데이터 검증 오류발생\n" . implode(
+ "\n",
+ $validation->getErrors()
+ ));
+ }
+ return $validation->getValidated();
+ }
+ protected function create_process(): void
+ {
+ $this->formDatas = $this->create_validate($this->action, $this->fields);
+ $this->entity = $this->getService()->create($this->formDatas);
+ }
+ protected function create_process_result($message): RedirectResponse|string
+ {
+ $url = strtolower(base_url() . $this->uri_path . $this->getService()->getClassName()) . "/view/" . $this->entity->getPK();
+ return redirect()->to($url)->with('error', $message);
+ }
+ protected function create_process_failed($message): RedirectResponse|string
+ {
+ return redirect()->back()->withInput()->with('error', $message);
+ }
+ final protected function create_procedure(): RedirectResponse|string
+ {
+ //Transaction Start
+ $this->getService()->getModel()->transStart();
+ try {
+ helper(['form']);
+ $this->create_process();
+ $this->getService()->getModel()->transCommit();
+ return $this->create_process_result(MESSAGES["SUCCESS"]);
+ } catch (\Exception $e) {
+ //Transaction Rollback
+ $this->getService()->getModel()->transRollback();
+ return $this->create_process_result($e->getMessage());
+ }
+ }
+ final public function create(): RedirectResponse|string
+ {
+ $this->create_init(__FUNCTION__);
+ return $this->create_procedure();
+ }
+
+ //수정관련
+ protected function modify_init(string $action, $fields = []): void
+ {
+ $this->init($action, $fields);
+ }
+ final public function modify_form(mixed $uid): RedirectResponse|string
+ {
+ $this->modify_init('modify');
+ return $this->modify_form_procedure($uid);
+ }
+ protected function modify_form_process(mixed $uid): void
+ {
+ $this->entity = $this->getService()->getModel()->getEntityByPK($uid);
+ if ($this->entity === null) {
+ throw new \Exception("해당 정보를 찾을수 없습니다.");
+ }
+ }
+ protected function modify_form_process_result(): string
+ {
+ return view(
+ $this->view_path . "modify",
+ data: ['viewDatas' => $this->getViewDatas()]
+ );
+ }
+ final protected function modify_form_procedure(mixed $uid): RedirectResponse|string
+ {
+ try {
+ helper(['form']);
+ $this->modify_form_process($uid);
+ $this->forms = ['attributes' => ['method' => "post",], 'hiddens' => []];
+ return $this->modify_form_process_result();
+ } catch (\Exception $e) {
+ return redirect()->back()->with('error', $e->getMessage());
+ }
+ }
+ final protected function modify_validate(string $action, array $fields): array
+ {
+ //변경할 값 확인 : Upload된 파일 검증시 $this->request->getVar()보다 먼처 체크필요
+ $validation = $this->getValidation($action, $fields);
+ if (!$validation->withRequest($this->request)->run()) {
+ throw new \Exception("{$this->getService()->getClassName()} 작업 데이터 검증 오류발생\n" . implode(
+ "\n",
+ $validation->getErrors()
+ ));
+ }
+ return $validation->getValidated();
+ }
+ //modify,toggle,batchjob 공통사용
+ protected function modify_process(mixed $uid): void
+ {
+ $this->formDatas = $this->modify_validate($this->action, $this->fields);
+ //자신정보정의
+ $this->entity = $this->getService()->getModel()->getEntityByPK($uid);
+ if ($this->entity === null) {
+ throw new \Exception(__FUNCTION__ . " => {$uid} 정보를 찾을수 없습니다.");
+ }
+ $this->entity = $this->getService()->modify($this->entity, $this->formDatas);
+ }
+ protected function modify_process_result($message): RedirectResponse|string
+ {
+ $url = strtolower(base_url() . $this->uri_path . $this->getService()->getClassName()) . "/view/" . $this->entity->getPK();
+ return redirect()->to($url)->with('error', $message);
+ }
+ protected function modify_process_failed($message): RedirectResponse|string
+ {
+ return redirect()->back()->withInput()->with('error', $message);
+ }
+ final protected function modify_procedure(mixed $uid): RedirectResponse|string
+ {
+ //Transaction Start
+ $this->getService()->getModel()->transStart();
+ try {
+ helper(['form']);
+ $this->modify_process($uid);
+ $this->getService()->getModel()->transCommit();
+ return $this->modify_process_result(MESSAGES["SUCCESS"]);
+ } catch (\Exception $e) {
+ //Transaction Rollback
+ $this->getService()->getModel()->transRollback();
+ return $this->modify_process_result($e->getMessage());
+ }
+ }
+ final public function modify(int $uid): RedirectResponse|string
+ {
+ $this->modify_init(__FUNCTION__);
+ return $this->modify_procedure($uid);
+ }
+
+ //단일필드작업
+ protected function toggle_process_result($message): RedirectResponse|string
+ {
+ return redirect()->to($this->myauth->popPreviousUrl())->with('error', $message);
+ }
+ protected function toggle_process_failed($message): RedirectResponse|string
+ {
+ return redirect()->back()->with('error', $message);
+ }
+ final protected function toggle_procedure(mixed $uid, string $field): RedirectResponse
+ {
+ //Transaction Start
+ $this->getService()->getModel()->transStart();
+ try {
+ $this->action = __FUNCTION__;
+ $this->fields = [$field];
+ $this->modify_process($uid);
+ $this->getService()->getModel()->transCommit();
+ return $this->toggle_process_result(MESSAGES["SUCCESS"]);
+ } catch (\Exception $e) {
+ //Transaction Rollback
+ $this->getService()->getModel()->transRollback();
+ return $this->toggle_process_failed($e->getMessage());
+ }
+ }
+ final public function toggle(mixed $uid, string $field): RedirectResponse|string
+ {
+ return $this->toggle_procedure($uid, $field);
+ }
+
+ //일괄처리작업
+ protected function batchjob_process_result($message): RedirectResponse|string
+ {
+ return redirect()->to($this->myauth->popPreviousUrl())->with('error', $message);
+ }
+ protected function batchjob_process_failed($message): RedirectResponse|string
+ {
+ return redirect()->back()->with('error', $message);
+ }
+ final protected function batchjob_procedure(): RedirectResponse
+ {
+ //Transaction Start
+ $this->getService()->getModel()->transStart();
+ try {
+ //데이터가 있는경우 Field만 처리하기위해
+ $fields = [];
+ foreach ($this->batchjob_fields as $field) {
+ if ($this->request->getVar($field)) {
+ $fields[$field] = $field;
+ }
+ }
+ $this->fields = $fields;
+ //변경할 UIDS
+ $uids = $this->request->getVar('batchjob_uids');
+ if (!$uids) {
+ throw new \Exception("적용할 리스트를 선택하셔야합니다.");
+ }
+ foreach (explode(",", $uids) as $uid) {
+ $this->modify_process($uid);
+ }
+ $this->getService()->getModel()->transCommit();
+ return $this->batchjob_process_result(MESSAGES["SUCCESS"]);
+ } catch (\Exception $e) {
+ //Transaction Rollback
+ $this->getService()->getModel()->transRollback();
+ return $this->batchjob_process_failed($e->getMessage());
+ }
+ }
+ //일괄처리작업
+ final public function batchjob(): RedirectResponse
+ {
+ $this->init(__FUNCTION__);
+ return $this->batchjob_procedure();
+ }
+
+ //삭제 delete,batchjob_delete 공통사용
+ protected function delete_process_result($message): RedirectResponse|string
+ {
+ return redirect()->to($this->myauth->popPreviousUrl())->with('error', $message);
+ }
+ protected function delete_process_failed($message): RedirectResponse|string
+ {
+ return redirect()->back()->with('error', $message);
+ }
+ protected function delete_process(mixed $uid): void
+ {
+ //자신정보정의
+ $this->entity = $this->getService()->getModel()->getEntityByPK($uid);
+ if ($this->entity === null) {
+ throw new \Exception("{$uid} 정보를 찾을수 없습니다.");
+ }
+ $this->entity = $this->getService()->delete($this->entity);
+ }
+ final protected function delete_procedure(mixed $uid): RedirectResponse|string
+ {
+ //Transaction Start
+ $this->getService()->getModel()->transStart();
+ try {
+ $this->delete_process($uid);
+ $this->getService()->getModel()->transCommit();
+ return $this->delete_process_result(MESSAGES["SUCCESS"]);
+ } catch (\Exception $e) {
+ //Transaction Rollback
+ $this->getService()->getModel()->transRollback();
+ return $this->delete_process_failed($e->getMessage());
+ }
+ }
+ final public function delete(mixed $uid): RedirectResponse|string
+ {
+ return $this->delete_procedure($uid);
+ }
+
+ //일괄삭제
+ protected function batchjob_delete_process_result($message): RedirectResponse|string
+ {
+ return redirect()->to($this->myauth->popPreviousUrl())->with('error', $message);
+ }
+ protected function batchjob_delete_process_failed($message): RedirectResponse|string
+ {
+ return redirect()->back()->with('error', $message);
+ }
+ final protected function batchjob_delete_procedure(): RedirectResponse|string
+ {
+ //Transaction Start
+ $this->getService()->getModel()->transStart();
+ try {
+ //변경할 UIDS
+ $uids = $this->request->getVar('batchjob_uids');
+ if (!$uids) {
+ throw new \Exception("적용할 리스트를 선택하셔야합니다.");
+ }
+ foreach (explode(",", $uids) as $uid) {
+ $this->delete_process($uid);
+ }
+ $this->getService()->getModel()->transCommit();
+ return $this->batchjob_delete_process_result(MESSAGES["SUCCESS"]);
+ } catch (\Exception $e) {
+ //Transaction Rollback
+ $this->getService()->getModel()->transRollback();
+ return $this->batchjob_delete_process_failed($e->getMessage());
+ }
+ }
+ final public function batchjob_delete(): RedirectResponse|string
+ {
+ return $this->batchjob_delete_procedure();
+ }
+
+ //View
+ protected function view_init(string $action, $fields = []): void
+ {
+ $this->init($action, $fields);
+ }
+ protected function view_process(mixed $uid): void
+ {
+ //자신정보정의
+ $this->entity = $this->getService()->getModel()->getEntityByPK($uid);
+ if ($this->entity === null) {
+ throw new \Exception("해당 정보를 찾을수 없습니다.");
+ }
+ }
+ protected function view_process_result(): string
+ {
+ helper(['form']);
+ $this->forms = ['attributes' => ['method' => "post",], 'hiddens' => []];
+ return view(
+ $this->view_path . "view",
+ data: ['viewDatas' => $this->getViewDatas()]
+ );
+ }
+ protected function view_process_failed($message): RedirectResponse|string
+ {
+ return redirect()->back()->with('error', $message);
+ }
+ final protected function view_procedure(mixed $uid): RedirectResponse|string
+ {
+ try {
+ $this->view_process($uid);
+ return $this->view_process_result();
+ } catch (\Exception $e) {
+ return $this->view_process_failed($e->getMessage());
+ }
+ }
+ final public function view(string $uid): RedirectResponse|string
+ {
+ $this->view_init(__FUNCTION__);
+ return $this->view_procedure($uid);
+ }
+
+ // 리스트
+ protected function index_init(string $action, $fields = []): void
+ {
+ $this->init($action, $fields);
+ }
+ //PageNation 처리
+ private function index_pagination_process($pager_group = 'default', int $segment = 0, $template = 'bootstrap_full'): string
+ {
+ //Page, Per_page필요부분
+ $this->page = (int) $this->request->getVar('page') ?: 1;
+ $this->per_page = (int) $this->request->getVar('per_page') ?: intval(env("mvc.default.list.per_page") ?? 10);
+ //줄수 처리용
+ $page_options = array("" => "줄수선택");
+ for ($i = $this->per_page; $i <= $this->total_count; $i += $this->per_page) {
+ $page_options[$i] = $i;
+ }
+ $page_options[$this->total_count] = $this->total_count;
+ $this->page_options = $page_options;
+ // 1.Views/Pagers/에 bootstrap_full.php,bootstrap_simple.php 생성
+ // 2.app/Config/Pager.php/$templates에 'bootstrap_full => 'Pagers\bootstrap_full',
+ // 'bootstrap_simple' => 'Pagers\bootstrap_simple', 추가
+ $pager = service("pager");
+ // $this->getService()->getModel()->paginate($this->per_page, $pager_group, $this->page, $segment);
+ $pager->makeLinks($this->page, $this->per_page, $this->total_count, $template, $segment, $pager_group);
+ $this->page = $pager->getCurrentPage($pager_group);
+ $this->total_page = $pager->getPageCount($pager_group);
+ return $pager->links($pager_group, $template);
+ }
+ protected function index_entitys_process_orderBy(): void
+ {
+ $this->order_field = $this->request->getVar('order_field') ?: DEFAULTS['EMPTY'];
+ $this->order_value = $this->request->getVar('order_value') ?: DEFAULTS['EMPTY'];
+ if ($this->order_field !== DEFAULTS['EMPTY'] && $this->order_value !== DEFAULTS['EMPTY']) {
+ $this->getService()->getModel()->orderBy(sprintf("%s.%s %s", $this->getService()->getModel()::TABLE, $this->order_field, $this->order_value));
+ } else {
+ $this->getService()->getModel()->orderBy(sprintf("%s.%s %s", $this->getService()->getModel()::TABLE, $this->getService()->getModel()::PK, "DESC"));
+ }
+ }
+ protected function index_entitys_process_limit(): void
+ {
+ $this->getService()->getModel()->limit($this->per_page, $this->page * $this->per_page - $this->per_page);
+ }
+ protected function index_entitys_process(): array
+ {
+ $this->getService()->setListConditon($this->request, $this->filter_fields);
+ $this->total = $this->getService()->setTotalCount();
+ //Sorting 처리
+ $this->index_entitys_process_orderBy();
+ //Limit관련
+ $this->index_entitys_process_limit();
+ $entitys = $this->getService()->getModel()->select($this->getService()->getModel()::TABLE . '.*')->findAll();
+ // log_message("debug", $this->getService()->getModel()->getLastQuery());
+ return $entitys;
+ }
+ protected function index_process_result(): string
+ {
+ return view(
+ $this->view_path . "index",
+ ['viewDatas' => $this->getViewDatas()]
+ );
+ }
+ final protected function index_procedure(): string
+ {
+ try {
+ helper(['form']);
+ //URL처리
+ $this->uri = $this->request->getUri();
+ //total 처리
+ $this->total_count = $this->getService()->setListCondition();
+ //pagenation 처리
+ $this->pagination = $this->index_pagination_process();
+ //모델 처리
+ $this->entitys = $this->index_entitys_process();
+ // 현재 URL을 스택에 저장
+ $this->myauth->pushCurrentUrl($this->request->getUri()->getPath() . ($this->request->getUri()->getQuery() ? "?" . $this->request->getUri()->getQuery() : ""));
+ return $this->index_process_result();
+ } catch (\Exception $e) {
+ return $this->helper->alert($e->getMessage());
+ }
+ }
+ public function index(): string
+ {
+ $this->index_init(__FUNCTION__);
+ return $this->index_procedure();
+ }
+
+ //OUPUT Document 관련
+ private function output_save_process(string $document_type, mixed $loaded_data): array
+ {
+ $full_path = WRITEPATH . DIRECTORY_SEPARATOR . "excel";
+ switch ($document_type) {
+ case 'excel':
+ $file_name = sprintf("%s_%s.xlsx", $this->getService()->getClassName(), date('Y-m-d_Hm'));
+ $writer = IOFactory::createWriter($loaded_data, 'Xlsx');
+ $writer->save($full_path . DIRECTORY_SEPARATOR . $file_name);
+ break;
+ case 'pdf':
+ $file_name = sprintf("%s_%s.pdf", $this->getService()->getClassName(), date('Y-m-d_Hm'));
+ $writer = new Mpdf($loaded_data);
+ $writer->save($full_path . DIRECTORY_SEPARATOR . $file_name);
+ break;
+ default:
+ if (!is_file($full_path)) {
+ throw new \Exception("첨부파일이 확인되지 않습니다.\n");
+ }
+ // //oupuput directly
+ // header("Content-Type: application/vnd.ms-excel");
+ // header(sprintf("Content-Disposition: attachment; filename=%s", urlencode($output_name)));
+ // header("Expires: 0");
+ // header("Cache-Control: must-revalidate");
+ // header("Pragma: public");
+ // header("Content-Length:" . filesize($full_path));
+ // return $writer->save('php://output');
+ break;
+ }
+ return array($full_path, $file_name);
+ }
+ //File Download관련
+ final protected function download_procedure(string $output_type, mixed $uid = false): DownloadResponse|RedirectResponse
+ {
+ try {
+ helper(['form']);
+ //URL처리
+ $this->uri = $this->request->getUri();
+ switch ($output_type) {
+ case 'excel':
+ case 'pdf':
+ // string buffer에서 읽어오는 경우
+ $this->entitys = $this->index_entitys_process();
+ $html = view(
+ 'templates' . DIRECTORY_SEPARATOR . $this->action,
+ ['viewDatas' => $this->getViewDatas()]
+ );
+ //data loading
+ $reader = new Html();
+ $loaded_data = $reader->loadFromString($html);
+ list($full_path, $file_name) = $this->output_save_process($output_type, $loaded_data);
+ $full_path .= DIRECTORY_SEPARATOR . $file_name;
+ break;
+ default:
+ if (!$uid) {
+ throw new \Exception("{$output_type}은 반드시 uid의 값이 필요합니다.");
+ }
+ $this->entity = $this->getService()->getModel()->getEntityByPK($uid);
+ if ($this->entity === null) {
+ throw new \Exception("{$uid} 정보를 찾을수 없습니다.");
+ }
+ list($file_name, $uploaded_filename) = $this->entity->getDownlaodFile();
+ $full_path = WRITEPATH . DIRECTORY_SEPARATOR . "uploads" . DIRECTORY_SEPARATOR . $uploaded_filename;
+ break;
+ }
+ return $this->response->download($full_path, null)->setFileName($file_name);
+ } catch (\Exception $e) {
+ return $this->helper->alert($e->getMessage());
+ }
+ }
+ // Download
+ final public function download(string $output_type, mixed $uid = false): DownloadResponse|string
+ {
+ $this->init(__FUNCTION__);
+ return $this->download_procedure($output_type, $uid);
+ }
+}
diff --git a/app/Controllers/Home.php b/app/Controllers/Home.php
new file mode 100644
index 0000000..5934333
--- /dev/null
+++ b/app/Controllers/Home.php
@@ -0,0 +1,11 @@
+title = lang("{$this->getService()->getClassPath()}.title");;
+ $this->helper = new UserHelper();
+ }
+ protected function getService(): UserService
+ {
+ if ($this->service === null) {
+ $this->service = new UserService();
+ $this->class_name = $this->service->getClassName();
+ $this->class_path = $this->service->getClassPath();
+ }
+ return $this->service;
+ }
+ protected function login_init(string $action, array $fields = []): void
+ {
+ $this->action = $action;
+ $fields = [
+ 'fields' => ['id', 'passwd'],
+ ];
+ $this->init($action, $fields);
+ }
+ //로그인화면
+ public function login_form(): RedirectResponse|string
+ {
+ try {
+ $this->login_init('login');
+ helper(['form']);
+ //구글 로그인 BUTTON용
+ $google_socket = new GoogleSocket();
+ $this->google_url = $google_socket->createAuthUrl();
+ $this->forms = ['attributes' => ['method' => "post",], 'hiddens' => []];
+ return view(
+ $this->view_path . "login",
+ data: ['viewDatas' => $this->getViewDatas()]
+ );
+ } catch (\Exception $e) {
+ log_message("error", $e->getMessage());
+ return redirect()->back()->withInput()->with('error', __FUNCTION__ . " 실패하였습니다.\n" . $e->getMessage());
+ }
+ }
+ //로그인처리
+ public function login(): RedirectResponse|string
+ {
+ try {
+ $this->login_init('login');
+ $this->formDatas = $this->create_validate($this->action, $this->fields);
+ $auth = new LocalService();
+ $auth->login($auth->checkUser($this->formDatas));
+ $this->message = "로그인 성공";
+ log_message("notice", __FUNCTION__ . $this->message);
+ // 이전 URL로 리다이렉트
+ return redirect()->to($this->myauth->popPreviousUrl())->with('message', $this->message);
+ } catch (\Exception $e) {
+ log_message("error", $e->getMessage());
+ return redirect()->back()->withInput()->with('error', __FUNCTION__ . " 실패하였습니다.\n" . $e->getMessage());
+ }
+ }
+ public function google_login(): RedirectResponse|string
+ {
+ try {
+ $access_code = $this->request->getVar('code');
+ if (!$access_code) {
+ throw new \Exception("구글 로그인 실패");
+ }
+ $auth = new GoogleService(new GoogleSocket());
+ $auth->login($auth->checkUser($access_code));
+ $this->message = "로그인 성공";
+ log_message("notice", __FUNCTION__ . $this->message);
+ // 이전 URL로 리다이렉트
+ return redirect()->to($this->myauth->popPreviousUrl())->with('message', $this->message);
+ } catch (\Exception $e) {
+ log_message("error", $e->getMessage());
+ return redirect()->back()->withInput()->with('error', __FUNCTION__ . " 실패하였습니다.\n" . $e->getMessage());
+ }
+ }
+ //로그아웃
+ public function logout(): RedirectResponse
+ {
+ try {
+ $auth = new LocalService();
+ $auth->logout();
+ // 성공 메시지 설정
+ $message = "로그아웃 되었습니다.";
+ // 홈페이지로 리다이렉트
+ return redirect()->route('/')->with('message', $message);
+ } catch (\Exception $e) {
+ log_message("error", $e->getMessage());
+ return redirect()->back()->with('error', "로그아웃 중 오류가 발생했습니다.");
+ }
+ }
+}
diff --git a/app/Database/Migrations/.gitkeep b/app/Database/Migrations/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/app/Database/Seeds/.gitkeep b/app/Database/Seeds/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/app/Database/erp2.sql b/app/Database/erp2.sql
new file mode 100644
index 0000000..2efa647
--- /dev/null
+++ b/app/Database/erp2.sql
@@ -0,0 +1,260 @@
+-- 1. 관리자정보
+CREATE TABLE userinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ id VARCHAR(20) NOT NULL UNIQUE,
+ passwd VARCHAR(255) NOT NULL,
+ name VARCHAR(20) NOT NULL,
+ email VARCHAR(50) NOT NULL UNIQUE,
+ mobile VARCHAR(20),
+ role ENUM('admin', 'manager') DEFAULT 'manager',
+ status ENUM('use', 'stop') DEFAULT 'use',
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+) COMMENT '관리자정보';
+
+-- 2. 고객정보
+CREATE TABLE clientinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ phone VARCHAR(50),
+ email VARCHAR(100),
+ account_balance INT DEFAULT 0 COMMENT '보증금',
+ role ENUM('user', 'partner') DEFAULT 'user',
+ note TEXT,
+ status ENUM('use', 'stop') DEFAULT 'use',
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+) COMMENT '고객정보';
+
+-- 3. 고객 입출금
+CREATE TABLE accountinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ clientinfo_uid INT NOT NULL,
+ status ENUM('deposit', 'withdrawal') DEFAULT 'deposit',
+ title VARCHAR(255) NOT NULL,
+ alias VARCHAR(50) NOT NULL COMMENT '입출금자명',
+ amount INT NOT NULL DEFAULT 0,
+ note TEXT,
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ CONSTRAINT FK_accountinfo_TO_clientinfo FOREIGN KEY (clientinfo_uid) REFERENCES clientinfo(uid)
+) COMMENT '입출금계좌';
+
+-- 4. 장비 정보
+CREATE TABLE deviceinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ type ENUM('server', 'vpc', 'kcs', 'network') DEFAULT 'server',
+ brand VARCHAR(50) DEFAULT 'HP',
+ model VARCHAR(50) NOT NULL,
+ cost_price INT DEFAULT 0,
+ price INT DEFAULT 0,
+ description TEXT,
+ status ENUM('use', 'stop') DEFAULT 'use',
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+) COMMENT '장비정보';
+
+-- 5. 장비 부속품 정보
+CREATE TABLE devicepartinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ type ENUM('memory', 'hdd', 'ssd', 'nic') DEFAULT 'memory',
+ brand VARCHAR(50) DEFAULT 'samsung',
+ capacity INT NOT NULL COMMENT '용량 (GB)',
+ cost_price INT DEFAULT 0,
+ price INT DEFAULT 0,
+ description TEXT,
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+) COMMENT '장비 부품 정보';
+
+-- 6. 장비 부속품 연결
+CREATE TABLE deviceinfos_accessories (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ deviceinfo_uid INT NOT NULL,
+ devicepartinfo_uid INT NOT NULL,
+ softwareinfo_uid INT,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (deviceinfo_uid) REFERENCES deviceinfo(uid),
+ FOREIGN KEY (devicepartinfo_uid) REFERENCES devicepartinfo(uid),
+ FOREIGN KEY (softwareinfo_uid) REFERENCES softwareinfo(uid)
+) COMMENT '장비-부속품 연결정보';
+
+-- 7. 소프트웨어 정보
+CREATE TABLE softwareinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ type ENUM('os', 'application') DEFAULT 'os',
+ title VARCHAR(100) NOT NULL,
+ cost_price INT DEFAULT 0,
+ price INT DEFAULT 0,
+ description TEXT,
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+) COMMENT '소프트웨어 정보';
+
+-- 8. 회선 정보
+CREATE TABLE lineinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ code VARCHAR(50) NOT NULL,
+ type ENUM('general', 'dedicated') DEFAULT 'general',
+ price INT DEFAULT 0,
+ status ENUM('use', 'stop') DEFAULT 'use',
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+) COMMENT '회선 정보';
+
+-- 9. IP 정보
+CREATE TABLE ipinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ lineinfo_uid INT NOT NULL,
+ deviceinfo_uid INT,
+ serviceinfo_uid INT,
+ ip_address VARCHAR(50) NOT NULL UNIQUE,
+ price INT DEFAULT 0,
+ status ENUM('use', 'stop') DEFAULT 'use',
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (lineinfo_uid) REFERENCES lineinfo(uid),
+ FOREIGN KEY (deviceinfo_uid) REFERENCES deviceinfo(uid),
+ FOREIGN KEY (serviceinfo_uid) REFERENCES serviceinfo(uid)
+) COMMENT 'IP 정보';
+
+-- 10. 상면 정보 (Rack Space)
+CREATE TABLE rackspaceinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ type ENUM('1u', '2u', '4u', 'fullrack', 'lightweight') DEFAULT '1u',
+ price INT DEFAULT 0,
+ status ENUM('use', 'stop') DEFAULT 'use',
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+) COMMENT '랙 상면 정보';
+
+-- 11. 서비스 정보
+CREATE TABLE serviceinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ clientinfo_uid INT NOT NULL,
+ type ENUM('hosting', 'colocation', 'defense') DEFAULT 'hosting',
+ title VARCHAR(100) NOT NULL,
+ payment_date DATE NOT NULL,
+ amount INT DEFAULT 0,
+ startdate_at TIMESTAMP NULL,
+ enddate_at TIMESTAMP NULL,
+ status ENUM('use', 'stop', 'terminate') DEFAULT 'use',
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (clientinfo_uid) REFERENCES clientinfo(uid)
+) COMMENT '서비스 정보';
+
+-- 12. 청구서 정보
+CREATE TABLE invoiceinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ serviceinfo_uid INT NOT NULL,
+ type ENUM('monthly', 'onetime', 'daily') DEFAULT 'monthly',
+ billing_amount INT DEFAULT 0,
+ description TEXT,
+ status ENUM('unpaid', 'paid', 'refunded') DEFAULT 'unpaid',
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (serviceinfo_uid) REFERENCES serviceinfo(uid)
+) COMMENT '청구서 정보';
+
+-- 13. 서비스 - 장비 연결
+CREATE TABLE serviceinfos_deviceinfos (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ serviceinfo_uid INT NOT NULL,
+ deviceinfo_uid INT NOT NULL,
+ payment_type ENUM('onetime', 'month', 'free') DEFAULT 'month',
+ amount INT DEFAULT 0,
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (serviceinfo_uid) REFERENCES serviceinfo(uid),
+ FOREIGN KEY (deviceinfo_uid) REFERENCES deviceinfo(uid)
+) COMMENT '서비스-장비 연결';
+
+-- 14. 서비스 - 장비 부품 연결
+CREATE TABLE serviceinfos_devicepartinfos (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ serviceinfo_uid INT NOT NULL,
+ devicepartinfo_uid INT NOT NULL,
+ payment_type ENUM('onetime', 'month', 'free') DEFAULT 'month',
+ amount INT DEFAULT 0,
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (serviceinfo_uid) REFERENCES serviceinfo(uid),
+ FOREIGN KEY (devicepartinfo_uid) REFERENCES devicepartinfo(uid)
+) COMMENT '서비스-장비 부품 연결';
+
+-- 15. 서비스 - 소프트웨어 연결
+CREATE TABLE serviceinfos_softwareinfos (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ serviceinfo_uid INT NOT NULL,
+ softwareinfo_uid INT NOT NULL,
+ payment_type ENUM('onetime', 'month', 'free') DEFAULT 'month',
+ amount INT DEFAULT 0,
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (serviceinfo_uid) REFERENCES serviceinfo(uid),
+ FOREIGN KEY (softwareinfo_uid) REFERENCES softwareinfo(uid)
+) COMMENT '서비스-소프트웨어 연결';
+
+-- 16. 서비스 - IP 연결
+CREATE TABLE serviceinfos_ipinfos (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ serviceinfo_uid INT NOT NULL,
+ ipinfo_uid INT NOT NULL,
+ payment_type ENUM('onetime', 'month', 'free') DEFAULT 'month',
+ amount INT DEFAULT 0,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (serviceinfo_uid) REFERENCES serviceinfo(uid),
+ FOREIGN KEY (ipinfo_uid) REFERENCES ipinfo(uid)
+) COMMENT '서비스-IP 연결';
+
+-- 17. 서비스 - 회선 연결
+CREATE TABLE serviceinfos_lineinfos (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ serviceinfo_uid INT NOT NULL,
+ lineinfo_uid INT NOT NULL,
+ payment_type ENUM('onetime', 'month', 'free') DEFAULT 'month',
+ amount INT DEFAULT 0,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (serviceinfo_uid) REFERENCES serviceinfo(uid),
+ FOREIGN KEY (lineinfo_uid) REFERENCES lineinfo(uid)
+) COMMENT '서비스-회선 연결';
+
+-- 18. 서비스 - 랙 상면 연결
+CREATE TABLE serviceinfos_rackinfos (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ serviceinfo_uid INT NOT NULL,
+ rackspaceinfo_uid INT NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (serviceinfo_uid) REFERENCES serviceinfo(uid),
+ FOREIGN KEY (rackspaceinfo_uid) REFERENCES rackspaceinfo(uid)
+) COMMENT '서비스-랙 연결';
+
+-- 19. 고객 이벤트 (포인트, 도메인 등)
+CREATE TABLE eventinfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ clientinfo_uid INT NOT NULL,
+ type ENUM('domain', 'point') DEFAULT 'point',
+ title VARCHAR(100) NOT NULL,
+ value INT NOT NULL,
+ note TEXT,
+ status ENUM('use', 'expired') DEFAULT 'use',
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (clientinfo_uid) REFERENCES clientinfo(uid)
+) COMMENT '고객 이벤트 (포인트/도메인)';
+
+-- 20. 작업 로그
+CREATE TABLE loginfo (
+ uid INT AUTO_INCREMENT PRIMARY KEY,
+ userinfo_uid INT NOT NULL,
+ type ENUM('info', 'warn', 'error', 'debug') DEFAULT 'info',
+ class VARCHAR(255),
+ method VARCHAR(255),
+ title VARCHAR(255) NOT NULL,
+ description TEXT NOT NULL,
+ status ENUM('use', 'archived') DEFAULT 'use',
+ updated_at TIMESTAMP NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (userinfo_uid) REFERENCES userinfo(uid)
+) COMMENT '작업 기록 로그';
diff --git a/app/Database/erp2.vuerd.json b/app/Database/erp2.vuerd.json
new file mode 100644
index 0000000..3b59746
--- /dev/null
+++ b/app/Database/erp2.vuerd.json
@@ -0,0 +1,5317 @@
+{
+ "$schema": "https://raw.githubusercontent.com/dineug/erd-editor/main/json-schema/schema.json",
+ "version": "3.0.0",
+ "settings": {
+ "width": 4000,
+ "height": 4000,
+ "scrollTop": -1393.7855,
+ "scrollLeft": -1341.8424,
+ "zoomLevel": 0.28,
+ "show": 431,
+ "database": 4,
+ "databaseName": "",
+ "canvasType": "ERD",
+ "language": 1,
+ "tableNameCase": 4,
+ "columnNameCase": 2,
+ "bracketType": 1,
+ "relationshipDataTypeSync": true,
+ "relationshipOptimization": false,
+ "columnOrder": [
+ 1,
+ 2,
+ 4,
+ 8,
+ 16,
+ 32,
+ 64
+ ],
+ "maxWidthComment": -1,
+ "ignoreSaveSettings": 0
+ },
+ "doc": {
+ "tableIds": [
+ "Jq5Qkun2FzQhCGKANIVOZ",
+ "6ajvOCaGuXU9pzV0Y9jEi",
+ "GDEF0_WuOpaYtsZxjn2zM",
+ "B4qGh3KZsXHQ3_4EOgwJZ",
+ "z6WAw_7GnarTYkag4Lcsg",
+ "IhXnqMFBU_GmCvISNyaKj",
+ "ZMGIWLFEswObjH2Sx0NlW",
+ "doERb3lIVeBW_D0NtNYX8",
+ "ZLEpY5EjuZV21718zf-Y1",
+ "koyEKtRkVwNF0GzL7x9EJ",
+ "kc1EFvFhlBSc0B0bDgX28",
+ "R4reSshLxH3DQW6fUfSPa",
+ "sgFc3Tg9sWiMm4hsEwKm9",
+ "aKTVtMqmPG7TJIsmxeVts",
+ "5KwHMmZppj-7TjRC_xQ54",
+ "3tdV9J9ns8BWCGQeCXITI",
+ "F82-EcEv3fB4uzGzPrPla",
+ "ttG9p8-yD8NJ7mGo3WNmM",
+ "jO40Ej5EXImXnadoJo9bn",
+ "gsa0XtQZQgrJ8ZXy8VQVg",
+ "h2unshrMDFXEq0pYzkkj1",
+ "jpnFKaKfog9MCHpL2kUNA"
+ ],
+ "relationshipIds": [
+ "gAVYXWnBSnCw-0ieO4Mil",
+ "lVmT5NRPuRWiB5-mz3uij",
+ "yTGnmFSvbFdY5rWfr010W",
+ "_WFB7yKFt3jKQzWvZ875h",
+ "WXDbQNvgLU6e2AEqZl8If",
+ "5zrgwg2t7XYDC0zIz0ORc",
+ "aKIaANWEYltTtJffBo7DN",
+ "2fpNq-aTVMnLCnp481j3_",
+ "-ehlfECxBa5BFIg6kMnvT",
+ "DpudF8iv_Pam0hOxkXJHx",
+ "qrtpEEOK_OBwxcghJlb0I",
+ "fXVyT3pzVo0T0fgoFz4Gi",
+ "gNCG3TpxxGPRdE0xNJM4Z",
+ "KG3dDhcIJxHk3e1aGPWPa",
+ "AckV69XB9r2m1VHP64gtd",
+ "6blFGW77QEOBG_yDwtFQq",
+ "95uRv2fz3rssNbkyuzkLh",
+ "Jj6R_4lq6u3hMzLKjgqc9",
+ "hHtOx5fLjgkRN3SIPGLfw",
+ "xRge-SlcORE83ke41txTG",
+ "6X-W5Q0SxPLd-sIRlwf5W",
+ "xoiTCIGWOuuUaMQlCDnhd",
+ "6DEBBDnRPVRt5gU5AuUcU"
+ ],
+ "indexIds": [],
+ "memoIds": []
+ },
+ "collections": {
+ "tableEntities": {
+ "Jq5Qkun2FzQhCGKANIVOZ": {
+ "id": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "userinfo",
+ "comment": "관리자정보",
+ "columnIds": [
+ "mfHtgzc_Aeocr6xkgwYWh",
+ "7DHaQCuxiYmzxE0eGpGxb",
+ "DkvMbsxXt6KWxlySb-X11",
+ "4KSZ7KgTh_tw7obM3dwuP",
+ "fBkXuryUQyHhfKXBjjKY2",
+ "vRWAi067oa76RKlyBllFH",
+ "5ZWglx6I92JkdajKWQyGq",
+ "GSZc6SEGQpz6SnTW-mo2n",
+ "-eAu93drXUckSR1goyAW0",
+ "_Isvu_q8_Gsp82W0Lkk-r"
+ ],
+ "seqColumnIds": [
+ "mfHtgzc_Aeocr6xkgwYWh",
+ "7DHaQCuxiYmzxE0eGpGxb",
+ "DkvMbsxXt6KWxlySb-X11",
+ "4KSZ7KgTh_tw7obM3dwuP",
+ "fBkXuryUQyHhfKXBjjKY2",
+ "vRWAi067oa76RKlyBllFH",
+ "5ZWglx6I92JkdajKWQyGq",
+ "GSZc6SEGQpz6SnTW-mo2n",
+ "-eAu93drXUckSR1goyAW0",
+ "_Isvu_q8_Gsp82W0Lkk-r"
+ ],
+ "ui": {
+ "x": 157.7353,
+ "y": 2182.2652,
+ "zIndex": 2,
+ "widthName": 60,
+ "widthComment": 62,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820135300,
+ "createAt": 1745819764136
+ }
+ },
+ "6ajvOCaGuXU9pzV0Y9jEi": {
+ "id": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "clientinfo",
+ "comment": "고객정보",
+ "columnIds": [
+ "_AcWUYKzNJd-V0fRHq8Cx",
+ "z-q_Ah0sghd0nR7VcCTLX",
+ "n61V7aSxLmcIeQqsYgAEE",
+ "5hP5ZiQGWDGf4HJrOiFb6",
+ "fMx3gLKi1fsfp7g26-QA9",
+ "xEoc72X0ErdGKU7rMzenm",
+ "Ksp65Qh_aOFmAb1ksx64Q",
+ "6JNus18UqajPfwOjkPali",
+ "qmfQUBHfeiJwR5kq3SyNW",
+ "D1tPn8Uq5C8UWh6dtzBJZ"
+ ],
+ "seqColumnIds": [
+ "_AcWUYKzNJd-V0fRHq8Cx",
+ "z-q_Ah0sghd0nR7VcCTLX",
+ "n61V7aSxLmcIeQqsYgAEE",
+ "5hP5ZiQGWDGf4HJrOiFb6",
+ "fMx3gLKi1fsfp7g26-QA9",
+ "xEoc72X0ErdGKU7rMzenm",
+ "Ksp65Qh_aOFmAb1ksx64Q",
+ "6JNus18UqajPfwOjkPali",
+ "qmfQUBHfeiJwR5kq3SyNW",
+ "D1tPn8Uq5C8UWh6dtzBJZ"
+ ],
+ "ui": {
+ "x": 142.5947,
+ "y": 110.6356,
+ "zIndex": 2,
+ "widthName": 60,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745821711948,
+ "createAt": 1745819764137
+ }
+ },
+ "GDEF0_WuOpaYtsZxjn2zM": {
+ "id": "GDEF0_WuOpaYtsZxjn2zM",
+ "name": "accountinfo",
+ "comment": "입출금계좌",
+ "columnIds": [
+ "ia3c6jjHvbTOX0cX4gbJl",
+ "nPYun5WHoy8uroXUBiqh8",
+ "hQM1tPHn6MrCa4pIL7t7w",
+ "tmkHXMc6fdNicHeig7nss",
+ "foBGnViYEjNlWIAhXeuLw",
+ "HaKM09bv5DwJFH9pxSqWI",
+ "f4NQ8F-FyamLyf4YR0VY1",
+ "Mr4qiMpKO4UVj3aZ3HueW",
+ "ut3UlHsNjxqNgAzENBkfy"
+ ],
+ "seqColumnIds": [
+ "ia3c6jjHvbTOX0cX4gbJl",
+ "nPYun5WHoy8uroXUBiqh8",
+ "hQM1tPHn6MrCa4pIL7t7w",
+ "tmkHXMc6fdNicHeig7nss",
+ "foBGnViYEjNlWIAhXeuLw",
+ "HaKM09bv5DwJFH9pxSqWI",
+ "f4NQ8F-FyamLyf4YR0VY1",
+ "Mr4qiMpKO4UVj3aZ3HueW",
+ "ut3UlHsNjxqNgAzENBkfy"
+ ],
+ "ui": {
+ "x": 1155.3674,
+ "y": 477.8964,
+ "zIndex": 2,
+ "widthName": 64,
+ "widthComment": 62,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820433236,
+ "createAt": 1745819764137
+ }
+ },
+ "B4qGh3KZsXHQ3_4EOgwJZ": {
+ "id": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "deviceinfo",
+ "comment": "장비정보",
+ "columnIds": [
+ "F9EPb6nsDx6Tf3GG8rvP1",
+ "BAzGsBrmLOwZGYLchLmyP",
+ "9F6QpQqxeEggZ0FHM81O1",
+ "6e3HgOnQwPQRS7r37pAK6",
+ "Wks-dXdsHSF-EATUWnxzY",
+ "lu2r9w2xmXsB8H7Mrdt1t",
+ "54iuIW4knok06vP4JH-oN",
+ "Djbw3B6xZWKAvwJDto9xl",
+ "tNaVOzr3vywCXiQdfUJWq",
+ "6bQ_6eGfINic9LpM6PtDw"
+ ],
+ "seqColumnIds": [
+ "F9EPb6nsDx6Tf3GG8rvP1",
+ "BAzGsBrmLOwZGYLchLmyP",
+ "9F6QpQqxeEggZ0FHM81O1",
+ "6e3HgOnQwPQRS7r37pAK6",
+ "Wks-dXdsHSF-EATUWnxzY",
+ "lu2r9w2xmXsB8H7Mrdt1t",
+ "54iuIW4knok06vP4JH-oN",
+ "Djbw3B6xZWKAvwJDto9xl",
+ "tNaVOzr3vywCXiQdfUJWq",
+ "6bQ_6eGfINic9LpM6PtDw"
+ ],
+ "ui": {
+ "x": 2192.4777,
+ "y": 1676.118,
+ "zIndex": 2,
+ "widthName": 60,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820689187,
+ "createAt": 1745819764137
+ }
+ },
+ "z6WAw_7GnarTYkag4Lcsg": {
+ "id": "z6WAw_7GnarTYkag4Lcsg",
+ "name": "devicepartinfo",
+ "comment": "장비 부품 정보",
+ "columnIds": [
+ "7fB6MgwIX6jMpD6d2Sq7k",
+ "ifLOIjTEKNJytRfXVcKLh",
+ "WypofEzhfxAMC-B6m6_Pa",
+ "la7gq1VzeEEcHsHq32cyZ",
+ "vEH63UEDTpNDwfohuydGV",
+ "6puy3O9sH_wHBHdoek3GL",
+ "VgoAzdbZTyH5EcsAYHxcO",
+ "vjqS4O1-xHzlAV_i9l3Xe",
+ "gQUT2v0Gg59R1SfKMhry3"
+ ],
+ "seqColumnIds": [
+ "7fB6MgwIX6jMpD6d2Sq7k",
+ "ifLOIjTEKNJytRfXVcKLh",
+ "WypofEzhfxAMC-B6m6_Pa",
+ "la7gq1VzeEEcHsHq32cyZ",
+ "vEH63UEDTpNDwfohuydGV",
+ "6puy3O9sH_wHBHdoek3GL",
+ "VgoAzdbZTyH5EcsAYHxcO",
+ "vjqS4O1-xHzlAV_i9l3Xe",
+ "gQUT2v0Gg59R1SfKMhry3"
+ ],
+ "ui": {
+ "x": 2204.5265,
+ "y": 2062.658,
+ "zIndex": 2,
+ "widthName": 78,
+ "widthComment": 81,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820399118,
+ "createAt": 1745819764138
+ }
+ },
+ "IhXnqMFBU_GmCvISNyaKj": {
+ "id": "IhXnqMFBU_GmCvISNyaKj",
+ "name": "deviceinfos_accessories",
+ "comment": "장비-부속품 연결정보",
+ "columnIds": [
+ "iylHjtnqU_oLEYolQkQIM",
+ "pDILaJt_-vUo0fH_c6t2O",
+ "5kPFX9LYoh8iinAEpt3lm",
+ "wQnQfh-JJI9BSQGaahMFu",
+ "NNGQ-RewVIApA1EISBQRS"
+ ],
+ "seqColumnIds": [
+ "iylHjtnqU_oLEYolQkQIM",
+ "pDILaJt_-vUo0fH_c6t2O",
+ "5kPFX9LYoh8iinAEpt3lm",
+ "wQnQfh-JJI9BSQGaahMFu",
+ "NNGQ-RewVIApA1EISBQRS"
+ ],
+ "ui": {
+ "x": 3153.684,
+ "y": 2284.0119,
+ "zIndex": 2,
+ "widthName": 125,
+ "widthComment": 118,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820410102,
+ "createAt": 1745819764138
+ }
+ },
+ "ZMGIWLFEswObjH2Sx0NlW": {
+ "id": "ZMGIWLFEswObjH2Sx0NlW",
+ "name": "softwareinfo",
+ "comment": "소프트웨어 정보",
+ "columnIds": [
+ "2HB01q46-mugMjuOz85YG",
+ "4acJag7ORjUzX7FP-gnhZ",
+ "1q8jG5dQKdD35_XYimkSk",
+ "zL9bBVm37HTSU-xWpwxxJ",
+ "G9PMddYQm9ohnzkJUa_nw",
+ "P84ZMnZu1nZtRhDY18T5o",
+ "k4vpMNZ75fNUjX-hrjXzs",
+ "8ZPjmeG3NoO6C0icGibJP"
+ ],
+ "seqColumnIds": [
+ "2HB01q46-mugMjuOz85YG",
+ "4acJag7ORjUzX7FP-gnhZ",
+ "1q8jG5dQKdD35_XYimkSk",
+ "zL9bBVm37HTSU-xWpwxxJ",
+ "G9PMddYQm9ohnzkJUa_nw",
+ "P84ZMnZu1nZtRhDY18T5o",
+ "k4vpMNZ75fNUjX-hrjXzs",
+ "8ZPjmeG3NoO6C0icGibJP"
+ ],
+ "ui": {
+ "x": 2206.3056,
+ "y": 2491.7422,
+ "zIndex": 2,
+ "widthName": 68,
+ "widthComment": 89,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820403103,
+ "createAt": 1745819764138
+ }
+ },
+ "doERb3lIVeBW_D0NtNYX8": {
+ "id": "doERb3lIVeBW_D0NtNYX8",
+ "name": "lineinfo",
+ "comment": "회선 정보",
+ "columnIds": [
+ "7B0zaLoZnOoMNW8OHZlrQ",
+ "XQE8sY3pDLC2iy95uc9Ir",
+ "oc5quhO8E3mqrBZKbIy_G",
+ "ehNv0f07ci1ARnkTSDG6J",
+ "xaUC3GLta1iHmo1YQ-qDo",
+ "ltPYBs_iNuZJM6wTnK0H-",
+ "liJON6hIBB9aS-pQgM0Q6"
+ ],
+ "seqColumnIds": [
+ "7B0zaLoZnOoMNW8OHZlrQ",
+ "XQE8sY3pDLC2iy95uc9Ir",
+ "oc5quhO8E3mqrBZKbIy_G",
+ "ehNv0f07ci1ARnkTSDG6J",
+ "xaUC3GLta1iHmo1YQ-qDo",
+ "ltPYBs_iNuZJM6wTnK0H-",
+ "liJON6hIBB9aS-pQgM0Q6"
+ ],
+ "ui": {
+ "x": 2182.9774,
+ "y": 1125.0705,
+ "zIndex": 2,
+ "widthName": 60,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820358708,
+ "createAt": 1745819764138
+ }
+ },
+ "ZLEpY5EjuZV21718zf-Y1": {
+ "id": "ZLEpY5EjuZV21718zf-Y1",
+ "name": "ipinfo",
+ "comment": "IP 정보",
+ "columnIds": [
+ "Id0h8QbOdlhPj9P1zTm5o",
+ "SFj3q5xg6pcI4RSDKPSgI",
+ "0ONL4QLQRyZ32MBJ7TN7u",
+ "e6eWKMFnpXI-rPJZ_9tBt",
+ "Vm1-FnoJLcJ0GRnTp0vnn",
+ "R-UjmO-S2UeQdddVNwH5M"
+ ],
+ "seqColumnIds": [
+ "Do7xvuwqsEo1L9PMTYoFg",
+ "Id0h8QbOdlhPj9P1zTm5o",
+ "SFj3q5xg6pcI4RSDKPSgI",
+ "0ONL4QLQRyZ32MBJ7TN7u",
+ "e6eWKMFnpXI-rPJZ_9tBt",
+ "Vm1-FnoJLcJ0GRnTp0vnn",
+ "R-UjmO-S2UeQdddVNwH5M"
+ ],
+ "ui": {
+ "x": 2191.6819,
+ "y": 1424.3551,
+ "zIndex": 2,
+ "widthName": 60,
+ "widthComment": 60,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745821118588,
+ "createAt": 1745819764138
+ }
+ },
+ "koyEKtRkVwNF0GzL7x9EJ": {
+ "id": "koyEKtRkVwNF0GzL7x9EJ",
+ "name": "rackspaceinfo",
+ "comment": "랙 상면 정보",
+ "columnIds": [
+ "kfY2wrPM9lt3o_CUkvoak",
+ "-AVLGqgg8iuXOxuNBdJWM",
+ "jVSNW9iqcbueNWWN9_Dwv",
+ "YuuqW6tFbr7O4iA-rdhqg",
+ "9alIPr6CfILtRlXhhAA9R",
+ "qGdOIB-Er06HQRP7mR09K"
+ ],
+ "seqColumnIds": [
+ "kfY2wrPM9lt3o_CUkvoak",
+ "-AVLGqgg8iuXOxuNBdJWM",
+ "jVSNW9iqcbueNWWN9_Dwv",
+ "YuuqW6tFbr7O4iA-rdhqg",
+ "9alIPr6CfILtRlXhhAA9R",
+ "qGdOIB-Er06HQRP7mR09K"
+ ],
+ "ui": {
+ "x": 2182.5045,
+ "y": 732.7384,
+ "zIndex": 2,
+ "widthName": 74,
+ "widthComment": 69,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820195936,
+ "createAt": 1745819764138
+ }
+ },
+ "kc1EFvFhlBSc0B0bDgX28": {
+ "id": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "serviceinfo",
+ "comment": "서비스 정보",
+ "columnIds": [
+ "nb5CGzskl3_LIRA0yyede",
+ "sscrxOdwLlx94tx1j_MrH",
+ "iM3NfWTfO6qrXv94EUFgk",
+ "CITRNBpXOZqGM6gHy5MlB",
+ "lwr6RuK8OGKJNLdd70NGS",
+ "sGkif4Lcd1cXyGgJQCuZl",
+ "xbFxL4RtJRdTlfQlDG3Ag",
+ "Yw-nVASb0K3Qf7KU2Vxto",
+ "jBxeJ8Sz7jRGrKBCkD1q1",
+ "mNL0XMdVPG6j_TTghhHg6",
+ "29LdworovSsHw2EaPP8Zv"
+ ],
+ "seqColumnIds": [
+ "nb5CGzskl3_LIRA0yyede",
+ "sscrxOdwLlx94tx1j_MrH",
+ "iM3NfWTfO6qrXv94EUFgk",
+ "CITRNBpXOZqGM6gHy5MlB",
+ "lwr6RuK8OGKJNLdd70NGS",
+ "sGkif4Lcd1cXyGgJQCuZl",
+ "xbFxL4RtJRdTlfQlDG3Ag",
+ "Yw-nVASb0K3Qf7KU2Vxto",
+ "jBxeJ8Sz7jRGrKBCkD1q1",
+ "mNL0XMdVPG6j_TTghhHg6",
+ "29LdworovSsHw2EaPP8Zv"
+ ],
+ "ui": {
+ "x": 168.9272,
+ "y": 824.4514,
+ "zIndex": 2,
+ "widthName": 60,
+ "widthComment": 65,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820420524,
+ "createAt": 1745819764139
+ }
+ },
+ "R4reSshLxH3DQW6fUfSPa": {
+ "id": "R4reSshLxH3DQW6fUfSPa",
+ "name": "invoiceinfo",
+ "comment": "청구서 정보",
+ "columnIds": [
+ "fsAJySlXPbGQahV59hQgo",
+ "bEnLVhafLMHZluEaYba4n",
+ "F6kponQqcXk2TT-AIElPY",
+ "hCs1Oji5S6161mXCnAgP6",
+ "h9_O9yvER5oW6Tb7ygofm",
+ "iDvGbVnpR-GTfqajd7P02",
+ "RcKYLal7wRQe2aYxGDKNl",
+ "2SU_tNQXyQlsQc6WchJ04"
+ ],
+ "seqColumnIds": [
+ "fsAJySlXPbGQahV59hQgo",
+ "bEnLVhafLMHZluEaYba4n",
+ "F6kponQqcXk2TT-AIElPY",
+ "hCs1Oji5S6161mXCnAgP6",
+ "h9_O9yvER5oW6Tb7ygofm",
+ "iDvGbVnpR-GTfqajd7P02",
+ "RcKYLal7wRQe2aYxGDKNl",
+ "2SU_tNQXyQlsQc6WchJ04"
+ ],
+ "ui": {
+ "x": 155.1454,
+ "y": 1748.7777,
+ "zIndex": 2,
+ "widthName": 60,
+ "widthComment": 65,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820132928,
+ "createAt": 1745819764139
+ }
+ },
+ "sgFc3Tg9sWiMm4hsEwKm9": {
+ "id": "sgFc3Tg9sWiMm4hsEwKm9",
+ "name": "serviceinfos_deviceinfos",
+ "comment": "서비스-장비 연결",
+ "columnIds": [
+ "TDXOYTNCKhN0r0vj8at-s",
+ "zG8_7CN0n4heTPXcS1V8e",
+ "uNqzMzAALwe_V_QA41OFW",
+ "N7bLm6kgwYVMp4xflIi_V",
+ "2VWaNAVGlic6PysNFB-p-",
+ "UCQyqc-F1swYRY6Qa3lIi"
+ ],
+ "seqColumnIds": [
+ "TDXOYTNCKhN0r0vj8at-s",
+ "zG8_7CN0n4heTPXcS1V8e",
+ "uNqzMzAALwe_V_QA41OFW",
+ "N7bLm6kgwYVMp4xflIi_V",
+ "2VWaNAVGlic6PysNFB-p-",
+ "uBuqi8eabZOjHwaEZ4HnE",
+ "UCQyqc-F1swYRY6Qa3lIi"
+ ],
+ "ui": {
+ "x": 1168.3116,
+ "y": 1591.8223,
+ "zIndex": 2,
+ "widthName": 128,
+ "widthComment": 94,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745821576034,
+ "createAt": 1745819764139
+ }
+ },
+ "aKTVtMqmPG7TJIsmxeVts": {
+ "id": "aKTVtMqmPG7TJIsmxeVts",
+ "name": "serviceinfos_devicepartinfos",
+ "comment": "서비스-장비 부품 연결",
+ "columnIds": [
+ "6NfFu4xWPfE3dgKI5J2hR",
+ "ywDnRAz0bbHUceo-EmLgZ",
+ "sn3p4UDiY4D9XJS4odHbP",
+ "_QMB8iNL4ACVQSKx9yLtt",
+ "SSHRhArhhTVREXVng48Yt",
+ "EJHor6ZpIKpTwVp6raA_S"
+ ],
+ "seqColumnIds": [
+ "6NfFu4xWPfE3dgKI5J2hR",
+ "ywDnRAz0bbHUceo-EmLgZ",
+ "sn3p4UDiY4D9XJS4odHbP",
+ "_QMB8iNL4ACVQSKx9yLtt",
+ "SSHRhArhhTVREXVng48Yt",
+ "oslDq1FY5cMlsObGiCwzd",
+ "EJHor6ZpIKpTwVp6raA_S"
+ ],
+ "ui": {
+ "x": 1175.8911,
+ "y": 1889.6665,
+ "zIndex": 2,
+ "widthName": 149,
+ "widthComment": 121,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745821579474,
+ "createAt": 1745819764139
+ }
+ },
+ "5KwHMmZppj-7TjRC_xQ54": {
+ "id": "5KwHMmZppj-7TjRC_xQ54",
+ "name": "serviceinfos_softwareinfos",
+ "comment": "서비스-소프트웨어 연결",
+ "columnIds": [
+ "BEKFy_-SDnnB_udIwHS4P",
+ "CgGKx59wNvLpWcoBrEuvK",
+ "AKpf8UbHiwRJll36PQR6f",
+ "8agbo_j1bQNrN8OoG2TAs",
+ "oZ3RpNTiLRp6utYj02FOu",
+ "3v3JWUBHg3mAb4HmHPUP-"
+ ],
+ "seqColumnIds": [
+ "BEKFy_-SDnnB_udIwHS4P",
+ "CgGKx59wNvLpWcoBrEuvK",
+ "AKpf8UbHiwRJll36PQR6f",
+ "8agbo_j1bQNrN8OoG2TAs",
+ "oZ3RpNTiLRp6utYj02FOu",
+ "bz9oU8libaYKgvU2IR82Y",
+ "3v3JWUBHg3mAb4HmHPUP-"
+ ],
+ "ui": {
+ "x": 1173.8733,
+ "y": 2195.3876,
+ "zIndex": 2,
+ "widthName": 139,
+ "widthComment": 130,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745821583978,
+ "createAt": 1745819764139
+ }
+ },
+ "3tdV9J9ns8BWCGQeCXITI": {
+ "id": "3tdV9J9ns8BWCGQeCXITI",
+ "name": "serviceinfos_ipinfos",
+ "comment": "서비스-IP 연결",
+ "columnIds": [
+ "N9whwkJk3imEwSl_tqk7W",
+ "jw-RY9uJDPlANghUUPnJ4",
+ "fdfaSp8HaDoxD96LL1tX4",
+ "6TYzDwJbiYyvcj6NuvaBI",
+ "zk7fke88oHwR5W_3ReKcp",
+ "kTwnu5ylJ22aQ7cBwn3pZ"
+ ],
+ "seqColumnIds": [
+ "N9whwkJk3imEwSl_tqk7W",
+ "jw-RY9uJDPlANghUUPnJ4",
+ "fdfaSp8HaDoxD96LL1tX4",
+ "6TYzDwJbiYyvcj6NuvaBI",
+ "zk7fke88oHwR5W_3ReKcp",
+ "kTwnu5ylJ22aQ7cBwn3pZ"
+ ],
+ "ui": {
+ "x": 1170.1167,
+ "y": 1293.2643,
+ "zIndex": 2,
+ "widthName": 104,
+ "widthComment": 80,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820427348,
+ "createAt": 1745819764139
+ }
+ },
+ "F82-EcEv3fB4uzGzPrPla": {
+ "id": "F82-EcEv3fB4uzGzPrPla",
+ "name": "serviceinfos_lineinfos",
+ "comment": "서비스-회선 연결",
+ "columnIds": [
+ "PQWVHSFO2ixiAvG2FPtNK",
+ "fDS7QeP4XnANQE_qEtGsY",
+ "dDoAacc03mr5Qr0bIwlN6",
+ "nAYYL4VvZwFBqqY9J5A1P",
+ "bfvSqmZKRGwglKHwbLVTz",
+ "iYSERwWFGJgDi9-uEJfTS"
+ ],
+ "seqColumnIds": [
+ "PQWVHSFO2ixiAvG2FPtNK",
+ "fDS7QeP4XnANQE_qEtGsY",
+ "dDoAacc03mr5Qr0bIwlN6",
+ "nAYYL4VvZwFBqqY9J5A1P",
+ "bfvSqmZKRGwglKHwbLVTz",
+ "iYSERwWFGJgDi9-uEJfTS"
+ ],
+ "ui": {
+ "x": 1168.4571,
+ "y": 1020.2791,
+ "zIndex": 2,
+ "widthName": 113,
+ "widthComment": 94,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745820429369,
+ "createAt": 1745819764139
+ }
+ },
+ "ttG9p8-yD8NJ7mGo3WNmM": {
+ "id": "ttG9p8-yD8NJ7mGo3WNmM",
+ "name": "serviceinfos_rackinfos",
+ "comment": "서비스-랙 연결",
+ "columnIds": [
+ "UAJUB0FsrZBOtW3odCNDB",
+ "JNQ2Viala7U_sALVNvoTK",
+ "gn7SpOC02ZjT2ajroWZ9Z",
+ "9_FF-LiFPrIftEIMWXpN8"
+ ],
+ "seqColumnIds": [
+ "UAJUB0FsrZBOtW3odCNDB",
+ "Boi84IhuWPlgjwsHQoE7r",
+ "0WQYrtV8bOVl-ZWieHr_Q",
+ "JNQ2Viala7U_sALVNvoTK",
+ "gn7SpOC02ZjT2ajroWZ9Z",
+ "9_FF-LiFPrIftEIMWXpN8"
+ ],
+ "ui": {
+ "x": 1161.9527,
+ "y": 819.519,
+ "zIndex": 2,
+ "widthName": 116,
+ "widthComment": 82,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745821562181,
+ "createAt": 1745819764139
+ }
+ },
+ "jO40Ej5EXImXnadoJo9bn": {
+ "id": "jO40Ej5EXImXnadoJo9bn",
+ "name": "eventinfo",
+ "comment": "고객 이벤트 (포인트/도메인)",
+ "columnIds": [
+ "9gNKhuq9UnDKyb9KuZ7cY",
+ "G3KLXJzl6S28Y8pN8hfy2",
+ "ALRvTZjYrv4K1ltFn30Mn",
+ "6ZDZLrSpVuAeUGWvifvq7",
+ "Dw-euSUHCJjMfHmPx9RZw",
+ "bc7vXbPHzkkSjtIb1Eu_C",
+ "1bvped58WqdQLlaObX0AT",
+ "iMCMcnQGafCPo46R91hOK",
+ "6WOE-jsg5rFe2X04atr-Y"
+ ],
+ "seqColumnIds": [
+ "9gNKhuq9UnDKyb9KuZ7cY",
+ "O_MKuQKv7yP-k0dsyszkk",
+ "G3KLXJzl6S28Y8pN8hfy2",
+ "ALRvTZjYrv4K1ltFn30Mn",
+ "3EFy0j6PlKaha31ajJSsZ",
+ "6ZDZLrSpVuAeUGWvifvq7",
+ "Dw-euSUHCJjMfHmPx9RZw",
+ "bc7vXbPHzkkSjtIb1Eu_C",
+ "1bvped58WqdQLlaObX0AT",
+ "iMCMcnQGafCPo46R91hOK",
+ "6WOE-jsg5rFe2X04atr-Y"
+ ],
+ "ui": {
+ "x": 2162.713,
+ "y": 109.7067,
+ "zIndex": 2,
+ "widthName": 60,
+ "widthComment": 152,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745821321519,
+ "createAt": 1745819764140
+ }
+ },
+ "gsa0XtQZQgrJ8ZXy8VQVg": {
+ "id": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "loginfo",
+ "comment": "작업 기록 로그",
+ "columnIds": [
+ "J_xS3cvULouXCTo5gCiTm",
+ "DGl10GI58QwOHwpTu4Z1Y",
+ "yMcXBh6lgSLWfLhrivvuX",
+ "KvMMNu-PKESt0_2K4AJcB",
+ "kKI4hKELPs9Nh5UtQvSu7",
+ "wkjLZ-Bc9g3Z6Rh_IQ7_q",
+ "W5bxFTggHoO9_PPsfy2EB",
+ "Wef1cEik-NFTr_alGxLpa",
+ "qHceMMaFcmVnWPlJ2T4Sg"
+ ],
+ "seqColumnIds": [
+ "J_xS3cvULouXCTo5gCiTm",
+ "DGl10GI58QwOHwpTu4Z1Y",
+ "yMcXBh6lgSLWfLhrivvuX",
+ "KvMMNu-PKESt0_2K4AJcB",
+ "kKI4hKELPs9Nh5UtQvSu7",
+ "wkjLZ-Bc9g3Z6Rh_IQ7_q",
+ "W5bxFTggHoO9_PPsfy2EB",
+ "Wef1cEik-NFTr_alGxLpa",
+ "OgECIC2AofE3lQIX-nOkM",
+ "qHceMMaFcmVnWPlJ2T4Sg"
+ ],
+ "ui": {
+ "x": 1172.834,
+ "y": 2553.8385,
+ "zIndex": 2,
+ "widthName": 60,
+ "widthComment": 81,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745821593690,
+ "createAt": 1745819764142
+ }
+ },
+ "h2unshrMDFXEq0pYzkkj1": {
+ "id": "h2unshrMDFXEq0pYzkkj1",
+ "name": "ipinfos_deviceinfos",
+ "comment": "IP 사용내역",
+ "columnIds": [
+ "0VUfC7ZS0NaIcqAhZYWHV",
+ "-VaB7wQ3cQKai3peK85u8",
+ "eh3Ubc6NIftsQEbE3kq-v",
+ "QZeSgl1hDBgLHn8iG-S4s"
+ ],
+ "seqColumnIds": [
+ "0VUfC7ZS0NaIcqAhZYWHV",
+ "-VaB7wQ3cQKai3peK85u8",
+ "eh3Ubc6NIftsQEbE3kq-v",
+ "QZeSgl1hDBgLHn8iG-S4s"
+ ],
+ "ui": {
+ "x": 2898.2473,
+ "y": 1581.7166,
+ "zIndex": 143,
+ "widthName": 102,
+ "widthComment": 63,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745821002309,
+ "createAt": 1745820710362
+ }
+ },
+ "jpnFKaKfog9MCHpL2kUNA": {
+ "id": "jpnFKaKfog9MCHpL2kUNA",
+ "name": "clientinfos_eventinfos",
+ "comment": "사용자별 이벤트정보",
+ "columnIds": [
+ "CvM2-6iCwD-tglwbA0p2z",
+ "DvgoXrAW5NmmXkGGuGg3V",
+ "SHZNfw8iUBzUTew32Qrz6"
+ ],
+ "seqColumnIds": [
+ "CvM2-6iCwD-tglwbA0p2z",
+ "DvgoXrAW5NmmXkGGuGg3V",
+ "SHZNfw8iUBzUTew32Qrz6"
+ ],
+ "ui": {
+ "x": 1156.7488,
+ "y": 132.2122,
+ "zIndex": 215,
+ "widthName": 116,
+ "widthComment": 113,
+ "color": ""
+ },
+ "meta": {
+ "updateAt": 1745821416069,
+ "createAt": 1745821326011
+ }
+ }
+ },
+ "tableColumnEntities": {
+ "mfHtgzc_Aeocr6xkgwYWh": {
+ "id": "mfHtgzc_Aeocr6xkgwYWh",
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764136,
+ "createAt": 1745819764136
+ }
+ },
+ "7DHaQCuxiYmzxE0eGpGxb": {
+ "id": "7DHaQCuxiYmzxE0eGpGxb",
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "id",
+ "comment": "",
+ "dataType": "VARCHAR(20)",
+ "default": "",
+ "options": 12,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764136,
+ "createAt": 1745819764136
+ }
+ },
+ "DkvMbsxXt6KWxlySb-X11": {
+ "id": "DkvMbsxXt6KWxlySb-X11",
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "passwd",
+ "comment": "",
+ "dataType": "VARCHAR(255)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764136,
+ "createAt": 1745819764136
+ }
+ },
+ "4KSZ7KgTh_tw7obM3dwuP": {
+ "id": "4KSZ7KgTh_tw7obM3dwuP",
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "name",
+ "comment": "",
+ "dataType": "VARCHAR(20)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "fBkXuryUQyHhfKXBjjKY2": {
+ "id": "fBkXuryUQyHhfKXBjjKY2",
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "email",
+ "comment": "",
+ "dataType": "VARCHAR(50)",
+ "default": "",
+ "options": 12,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "vRWAi067oa76RKlyBllFH": {
+ "id": "vRWAi067oa76RKlyBllFH",
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "mobile",
+ "comment": "",
+ "dataType": "VARCHAR(20)",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "5ZWglx6I92JkdajKWQyGq": {
+ "id": "5ZWglx6I92JkdajKWQyGq",
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "role",
+ "comment": "",
+ "dataType": "ENUM(admin,manager)",
+ "default": "manager",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 126,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "GSZc6SEGQpz6SnTW-mo2n": {
+ "id": "GSZc6SEGQpz6SnTW-mo2n",
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(use,stop)",
+ "default": "use",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 87,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "-eAu93drXUckSR1goyAW0": {
+ "id": "-eAu93drXUckSR1goyAW0",
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "_Isvu_q8_Gsp82W0Lkk-r": {
+ "id": "_Isvu_q8_Gsp82W0Lkk-r",
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "_AcWUYKzNJd-V0fRHq8Cx": {
+ "id": "_AcWUYKzNJd-V0fRHq8Cx",
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "n61V7aSxLmcIeQqsYgAEE": {
+ "id": "n61V7aSxLmcIeQqsYgAEE",
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "name",
+ "comment": "",
+ "dataType": "VARCHAR(100)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "5hP5ZiQGWDGf4HJrOiFb6": {
+ "id": "5hP5ZiQGWDGf4HJrOiFb6",
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "phone",
+ "comment": "",
+ "dataType": "VARCHAR(50)",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "fMx3gLKi1fsfp7g26-QA9": {
+ "id": "fMx3gLKi1fsfp7g26-QA9",
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "email",
+ "comment": "",
+ "dataType": "VARCHAR(100)",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "xEoc72X0ErdGKU7rMzenm": {
+ "id": "xEoc72X0ErdGKU7rMzenm",
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "account_balance",
+ "comment": "보증금",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 90,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "z-q_Ah0sghd0nR7VcCTLX": {
+ "id": "z-q_Ah0sghd0nR7VcCTLX",
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "role",
+ "comment": "",
+ "dataType": "ENUM(user,partner)",
+ "default": "user",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 107,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821700050,
+ "createAt": 1745819764137
+ }
+ },
+ "Ksp65Qh_aOFmAb1ksx64Q": {
+ "id": "Ksp65Qh_aOFmAb1ksx64Q",
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "note",
+ "comment": "",
+ "dataType": "TEXT",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "6JNus18UqajPfwOjkPali": {
+ "id": "6JNus18UqajPfwOjkPali",
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(use,stop)",
+ "default": "use",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 87,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821688154,
+ "createAt": 1745819764137
+ }
+ },
+ "qmfQUBHfeiJwR5kq3SyNW": {
+ "id": "qmfQUBHfeiJwR5kq3SyNW",
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "D1tPn8Uq5C8UWh6dtzBJZ": {
+ "id": "D1tPn8Uq5C8UWh6dtzBJZ",
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "ia3c6jjHvbTOX0cX4gbJl": {
+ "id": "ia3c6jjHvbTOX0cX4gbJl",
+ "tableId": "GDEF0_WuOpaYtsZxjn2zM",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "nPYun5WHoy8uroXUBiqh8": {
+ "id": "nPYun5WHoy8uroXUBiqh8",
+ "tableId": "GDEF0_WuOpaYtsZxjn2zM",
+ "name": "clientinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 73,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "hQM1tPHn6MrCa4pIL7t7w": {
+ "id": "hQM1tPHn6MrCa4pIL7t7w",
+ "tableId": "GDEF0_WuOpaYtsZxjn2zM",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(deposit,withdrawal)",
+ "default": "deposit",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 143,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "tmkHXMc6fdNicHeig7nss": {
+ "id": "tmkHXMc6fdNicHeig7nss",
+ "tableId": "GDEF0_WuOpaYtsZxjn2zM",
+ "name": "title",
+ "comment": "",
+ "dataType": "VARCHAR(255)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "foBGnViYEjNlWIAhXeuLw": {
+ "id": "foBGnViYEjNlWIAhXeuLw",
+ "tableId": "GDEF0_WuOpaYtsZxjn2zM",
+ "name": "alias",
+ "comment": "입출금자명",
+ "dataType": "VARCHAR(50)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 62,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "HaKM09bv5DwJFH9pxSqWI": {
+ "id": "HaKM09bv5DwJFH9pxSqWI",
+ "tableId": "GDEF0_WuOpaYtsZxjn2zM",
+ "name": "amount",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "f4NQ8F-FyamLyf4YR0VY1": {
+ "id": "f4NQ8F-FyamLyf4YR0VY1",
+ "tableId": "GDEF0_WuOpaYtsZxjn2zM",
+ "name": "note",
+ "comment": "",
+ "dataType": "TEXT",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "Mr4qiMpKO4UVj3aZ3HueW": {
+ "id": "Mr4qiMpKO4UVj3aZ3HueW",
+ "tableId": "GDEF0_WuOpaYtsZxjn2zM",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "ut3UlHsNjxqNgAzENBkfy": {
+ "id": "ut3UlHsNjxqNgAzENBkfy",
+ "tableId": "GDEF0_WuOpaYtsZxjn2zM",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "F9EPb6nsDx6Tf3GG8rvP1": {
+ "id": "F9EPb6nsDx6Tf3GG8rvP1",
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764137,
+ "createAt": 1745819764137
+ }
+ },
+ "BAzGsBrmLOwZGYLchLmyP": {
+ "id": "BAzGsBrmLOwZGYLchLmyP",
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "type",
+ "comment": "",
+ "dataType": "ENUM(server,vpc,kcs,network)",
+ "default": "server",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 161,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "9F6QpQqxeEggZ0FHM81O1": {
+ "id": "9F6QpQqxeEggZ0FHM81O1",
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "brand",
+ "comment": "",
+ "dataType": "VARCHAR(50)",
+ "default": "HP",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "6e3HgOnQwPQRS7r37pAK6": {
+ "id": "6e3HgOnQwPQRS7r37pAK6",
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "model",
+ "comment": "",
+ "dataType": "VARCHAR(50)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "Wks-dXdsHSF-EATUWnxzY": {
+ "id": "Wks-dXdsHSF-EATUWnxzY",
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "cost_price",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "lu2r9w2xmXsB8H7Mrdt1t": {
+ "id": "lu2r9w2xmXsB8H7Mrdt1t",
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "price",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "54iuIW4knok06vP4JH-oN": {
+ "id": "54iuIW4knok06vP4JH-oN",
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "description",
+ "comment": "",
+ "dataType": "TEXT",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 61,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "Djbw3B6xZWKAvwJDto9xl": {
+ "id": "Djbw3B6xZWKAvwJDto9xl",
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(use,stop)",
+ "default": "use",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 87,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "tNaVOzr3vywCXiQdfUJWq": {
+ "id": "tNaVOzr3vywCXiQdfUJWq",
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "6bQ_6eGfINic9LpM6PtDw": {
+ "id": "6bQ_6eGfINic9LpM6PtDw",
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "7fB6MgwIX6jMpD6d2Sq7k": {
+ "id": "7fB6MgwIX6jMpD6d2Sq7k",
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "ifLOIjTEKNJytRfXVcKLh": {
+ "id": "ifLOIjTEKNJytRfXVcKLh",
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "name": "type",
+ "comment": "",
+ "dataType": "ENUM(memory,hdd,ssd,nic)",
+ "default": "memory",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 148,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "WypofEzhfxAMC-B6m6_Pa": {
+ "id": "WypofEzhfxAMC-B6m6_Pa",
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "name": "brand",
+ "comment": "",
+ "dataType": "VARCHAR(50)",
+ "default": "samsung",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "la7gq1VzeEEcHsHq32cyZ": {
+ "id": "la7gq1VzeEEcHsHq32cyZ",
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "name": "capacity",
+ "comment": "용량 (GB)",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "vEH63UEDTpNDwfohuydGV": {
+ "id": "vEH63UEDTpNDwfohuydGV",
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "name": "cost_price",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "6puy3O9sH_wHBHdoek3GL": {
+ "id": "6puy3O9sH_wHBHdoek3GL",
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "name": "price",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "VgoAzdbZTyH5EcsAYHxcO": {
+ "id": "VgoAzdbZTyH5EcsAYHxcO",
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "name": "description",
+ "comment": "",
+ "dataType": "TEXT",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 61,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "vjqS4O1-xHzlAV_i9l3Xe": {
+ "id": "vjqS4O1-xHzlAV_i9l3Xe",
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "gQUT2v0Gg59R1SfKMhry3": {
+ "id": "gQUT2v0Gg59R1SfKMhry3",
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "iylHjtnqU_oLEYolQkQIM": {
+ "id": "iylHjtnqU_oLEYolQkQIM",
+ "tableId": "IhXnqMFBU_GmCvISNyaKj",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "pDILaJt_-vUo0fH_c6t2O": {
+ "id": "pDILaJt_-vUo0fH_c6t2O",
+ "tableId": "IhXnqMFBU_GmCvISNyaKj",
+ "name": "deviceinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 78,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "5kPFX9LYoh8iinAEpt3lm": {
+ "id": "5kPFX9LYoh8iinAEpt3lm",
+ "tableId": "IhXnqMFBU_GmCvISNyaKj",
+ "name": "devicepartinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 99,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "wQnQfh-JJI9BSQGaahMFu": {
+ "id": "wQnQfh-JJI9BSQGaahMFu",
+ "tableId": "IhXnqMFBU_GmCvISNyaKj",
+ "name": "softwareinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 2,
+ "widthName": 89,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "NNGQ-RewVIApA1EISBQRS": {
+ "id": "NNGQ-RewVIApA1EISBQRS",
+ "tableId": "IhXnqMFBU_GmCvISNyaKj",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "2HB01q46-mugMjuOz85YG": {
+ "id": "2HB01q46-mugMjuOz85YG",
+ "tableId": "ZMGIWLFEswObjH2Sx0NlW",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "4acJag7ORjUzX7FP-gnhZ": {
+ "id": "4acJag7ORjUzX7FP-gnhZ",
+ "tableId": "ZMGIWLFEswObjH2Sx0NlW",
+ "name": "type",
+ "comment": "",
+ "dataType": "ENUM(os,application)",
+ "default": "os",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 117,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "1q8jG5dQKdD35_XYimkSk": {
+ "id": "1q8jG5dQKdD35_XYimkSk",
+ "tableId": "ZMGIWLFEswObjH2Sx0NlW",
+ "name": "title",
+ "comment": "",
+ "dataType": "VARCHAR(100)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "zL9bBVm37HTSU-xWpwxxJ": {
+ "id": "zL9bBVm37HTSU-xWpwxxJ",
+ "tableId": "ZMGIWLFEswObjH2Sx0NlW",
+ "name": "cost_price",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "G9PMddYQm9ohnzkJUa_nw": {
+ "id": "G9PMddYQm9ohnzkJUa_nw",
+ "tableId": "ZMGIWLFEswObjH2Sx0NlW",
+ "name": "price",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "P84ZMnZu1nZtRhDY18T5o": {
+ "id": "P84ZMnZu1nZtRhDY18T5o",
+ "tableId": "ZMGIWLFEswObjH2Sx0NlW",
+ "name": "description",
+ "comment": "",
+ "dataType": "TEXT",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 61,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "k4vpMNZ75fNUjX-hrjXzs": {
+ "id": "k4vpMNZ75fNUjX-hrjXzs",
+ "tableId": "ZMGIWLFEswObjH2Sx0NlW",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "8ZPjmeG3NoO6C0icGibJP": {
+ "id": "8ZPjmeG3NoO6C0icGibJP",
+ "tableId": "ZMGIWLFEswObjH2Sx0NlW",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "7B0zaLoZnOoMNW8OHZlrQ": {
+ "id": "7B0zaLoZnOoMNW8OHZlrQ",
+ "tableId": "doERb3lIVeBW_D0NtNYX8",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "XQE8sY3pDLC2iy95uc9Ir": {
+ "id": "XQE8sY3pDLC2iy95uc9Ir",
+ "tableId": "doERb3lIVeBW_D0NtNYX8",
+ "name": "code",
+ "comment": "",
+ "dataType": "VARCHAR(50)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "oc5quhO8E3mqrBZKbIy_G": {
+ "id": "oc5quhO8E3mqrBZKbIy_G",
+ "tableId": "doERb3lIVeBW_D0NtNYX8",
+ "name": "type",
+ "comment": "",
+ "dataType": "ENUM(general,dedicated)",
+ "default": "general",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 138,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "ehNv0f07ci1ARnkTSDG6J": {
+ "id": "ehNv0f07ci1ARnkTSDG6J",
+ "tableId": "doERb3lIVeBW_D0NtNYX8",
+ "name": "price",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "xaUC3GLta1iHmo1YQ-qDo": {
+ "id": "xaUC3GLta1iHmo1YQ-qDo",
+ "tableId": "doERb3lIVeBW_D0NtNYX8",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(use,stop)",
+ "default": "use",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 87,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "ltPYBs_iNuZJM6wTnK0H-": {
+ "id": "ltPYBs_iNuZJM6wTnK0H-",
+ "tableId": "doERb3lIVeBW_D0NtNYX8",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "liJON6hIBB9aS-pQgM0Q6": {
+ "id": "liJON6hIBB9aS-pQgM0Q6",
+ "tableId": "doERb3lIVeBW_D0NtNYX8",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "Do7xvuwqsEo1L9PMTYoFg": {
+ "id": "Do7xvuwqsEo1L9PMTYoFg",
+ "tableId": "ZLEpY5EjuZV21718zf-Y1",
+ "name": "uid",
+ "comment": "",
+ "dataType": "",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745820641243,
+ "createAt": 1745819764138
+ }
+ },
+ "kfY2wrPM9lt3o_CUkvoak": {
+ "id": "kfY2wrPM9lt3o_CUkvoak",
+ "tableId": "koyEKtRkVwNF0GzL7x9EJ",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764138,
+ "createAt": 1745819764138
+ }
+ },
+ "-AVLGqgg8iuXOxuNBdJWM": {
+ "id": "-AVLGqgg8iuXOxuNBdJWM",
+ "tableId": "koyEKtRkVwNF0GzL7x9EJ",
+ "name": "type",
+ "comment": "",
+ "dataType": "ENUM(1u,2u,4u,fullrack,lightweight)",
+ "default": "1u",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 191,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "jVSNW9iqcbueNWWN9_Dwv": {
+ "id": "jVSNW9iqcbueNWWN9_Dwv",
+ "tableId": "koyEKtRkVwNF0GzL7x9EJ",
+ "name": "price",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "YuuqW6tFbr7O4iA-rdhqg": {
+ "id": "YuuqW6tFbr7O4iA-rdhqg",
+ "tableId": "koyEKtRkVwNF0GzL7x9EJ",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(use,stop)",
+ "default": "use",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 87,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "9alIPr6CfILtRlXhhAA9R": {
+ "id": "9alIPr6CfILtRlXhhAA9R",
+ "tableId": "koyEKtRkVwNF0GzL7x9EJ",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "qGdOIB-Er06HQRP7mR09K": {
+ "id": "qGdOIB-Er06HQRP7mR09K",
+ "tableId": "koyEKtRkVwNF0GzL7x9EJ",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "nb5CGzskl3_LIRA0yyede": {
+ "id": "nb5CGzskl3_LIRA0yyede",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "sscrxOdwLlx94tx1j_MrH": {
+ "id": "sscrxOdwLlx94tx1j_MrH",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "clientinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 73,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "iM3NfWTfO6qrXv94EUFgk": {
+ "id": "iM3NfWTfO6qrXv94EUFgk",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "type",
+ "comment": "",
+ "dataType": "ENUM(hosting,colocation,defense)",
+ "default": "hosting",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 185,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "CITRNBpXOZqGM6gHy5MlB": {
+ "id": "CITRNBpXOZqGM6gHy5MlB",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "title",
+ "comment": "",
+ "dataType": "VARCHAR(100)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "lwr6RuK8OGKJNLdd70NGS": {
+ "id": "lwr6RuK8OGKJNLdd70NGS",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "payment_date",
+ "comment": "",
+ "dataType": "DATE",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 77,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "sGkif4Lcd1cXyGgJQCuZl": {
+ "id": "sGkif4Lcd1cXyGgJQCuZl",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "amount",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "xbFxL4RtJRdTlfQlDG3Ag": {
+ "id": "xbFxL4RtJRdTlfQlDG3Ag",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "startdate_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 64,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "Yw-nVASb0K3Qf7KU2Vxto": {
+ "id": "Yw-nVASb0K3Qf7KU2Vxto",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "enddate_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 61,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "jBxeJ8Sz7jRGrKBCkD1q1": {
+ "id": "jBxeJ8Sz7jRGrKBCkD1q1",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(use,stop,terminate)",
+ "default": "use",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 141,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "mNL0XMdVPG6j_TTghhHg6": {
+ "id": "mNL0XMdVPG6j_TTghhHg6",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "29LdworovSsHw2EaPP8Zv": {
+ "id": "29LdworovSsHw2EaPP8Zv",
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "fsAJySlXPbGQahV59hQgo": {
+ "id": "fsAJySlXPbGQahV59hQgo",
+ "tableId": "R4reSshLxH3DQW6fUfSPa",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "bEnLVhafLMHZluEaYba4n": {
+ "id": "bEnLVhafLMHZluEaYba4n",
+ "tableId": "R4reSshLxH3DQW6fUfSPa",
+ "name": "serviceinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 80,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "F6kponQqcXk2TT-AIElPY": {
+ "id": "F6kponQqcXk2TT-AIElPY",
+ "tableId": "R4reSshLxH3DQW6fUfSPa",
+ "name": "type",
+ "comment": "",
+ "dataType": "ENUM(monthly,onetime,daily)",
+ "default": "monthly",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 161,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "hCs1Oji5S6161mXCnAgP6": {
+ "id": "hCs1Oji5S6161mXCnAgP6",
+ "tableId": "R4reSshLxH3DQW6fUfSPa",
+ "name": "billing_amount",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 81,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "h9_O9yvER5oW6Tb7ygofm": {
+ "id": "h9_O9yvER5oW6Tb7ygofm",
+ "tableId": "R4reSshLxH3DQW6fUfSPa",
+ "name": "description",
+ "comment": "",
+ "dataType": "TEXT",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 61,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "iDvGbVnpR-GTfqajd7P02": {
+ "id": "iDvGbVnpR-GTfqajd7P02",
+ "tableId": "R4reSshLxH3DQW6fUfSPa",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(unpaid,paid,refunded)",
+ "default": "unpaid",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 157,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "RcKYLal7wRQe2aYxGDKNl": {
+ "id": "RcKYLal7wRQe2aYxGDKNl",
+ "tableId": "R4reSshLxH3DQW6fUfSPa",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "2SU_tNQXyQlsQc6WchJ04": {
+ "id": "2SU_tNQXyQlsQc6WchJ04",
+ "tableId": "R4reSshLxH3DQW6fUfSPa",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "TDXOYTNCKhN0r0vj8at-s": {
+ "id": "TDXOYTNCKhN0r0vj8at-s",
+ "tableId": "sgFc3Tg9sWiMm4hsEwKm9",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "zG8_7CN0n4heTPXcS1V8e": {
+ "id": "zG8_7CN0n4heTPXcS1V8e",
+ "tableId": "sgFc3Tg9sWiMm4hsEwKm9",
+ "name": "serviceinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 80,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "uNqzMzAALwe_V_QA41OFW": {
+ "id": "uNqzMzAALwe_V_QA41OFW",
+ "tableId": "sgFc3Tg9sWiMm4hsEwKm9",
+ "name": "deviceinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 78,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "N7bLm6kgwYVMp4xflIi_V": {
+ "id": "N7bLm6kgwYVMp4xflIi_V",
+ "tableId": "sgFc3Tg9sWiMm4hsEwKm9",
+ "name": "payment_type",
+ "comment": "",
+ "dataType": "ENUM(onetime,month,free)",
+ "default": "month",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 77,
+ "widthComment": 60,
+ "widthDataType": 148,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "2VWaNAVGlic6PysNFB-p-": {
+ "id": "2VWaNAVGlic6PysNFB-p-",
+ "tableId": "sgFc3Tg9sWiMm4hsEwKm9",
+ "name": "amount",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "uBuqi8eabZOjHwaEZ4HnE": {
+ "id": "uBuqi8eabZOjHwaEZ4HnE",
+ "tableId": "sgFc3Tg9sWiMm4hsEwKm9",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "UCQyqc-F1swYRY6Qa3lIi": {
+ "id": "UCQyqc-F1swYRY6Qa3lIi",
+ "tableId": "sgFc3Tg9sWiMm4hsEwKm9",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "6NfFu4xWPfE3dgKI5J2hR": {
+ "id": "6NfFu4xWPfE3dgKI5J2hR",
+ "tableId": "aKTVtMqmPG7TJIsmxeVts",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "ywDnRAz0bbHUceo-EmLgZ": {
+ "id": "ywDnRAz0bbHUceo-EmLgZ",
+ "tableId": "aKTVtMqmPG7TJIsmxeVts",
+ "name": "serviceinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 80,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "sn3p4UDiY4D9XJS4odHbP": {
+ "id": "sn3p4UDiY4D9XJS4odHbP",
+ "tableId": "aKTVtMqmPG7TJIsmxeVts",
+ "name": "devicepartinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 99,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "_QMB8iNL4ACVQSKx9yLtt": {
+ "id": "_QMB8iNL4ACVQSKx9yLtt",
+ "tableId": "aKTVtMqmPG7TJIsmxeVts",
+ "name": "payment_type",
+ "comment": "",
+ "dataType": "ENUM(onetime,month,free)",
+ "default": "month",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 77,
+ "widthComment": 60,
+ "widthDataType": 148,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "SSHRhArhhTVREXVng48Yt": {
+ "id": "SSHRhArhhTVREXVng48Yt",
+ "tableId": "aKTVtMqmPG7TJIsmxeVts",
+ "name": "amount",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "oslDq1FY5cMlsObGiCwzd": {
+ "id": "oslDq1FY5cMlsObGiCwzd",
+ "tableId": "aKTVtMqmPG7TJIsmxeVts",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "EJHor6ZpIKpTwVp6raA_S": {
+ "id": "EJHor6ZpIKpTwVp6raA_S",
+ "tableId": "aKTVtMqmPG7TJIsmxeVts",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "BEKFy_-SDnnB_udIwHS4P": {
+ "id": "BEKFy_-SDnnB_udIwHS4P",
+ "tableId": "5KwHMmZppj-7TjRC_xQ54",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "CgGKx59wNvLpWcoBrEuvK": {
+ "id": "CgGKx59wNvLpWcoBrEuvK",
+ "tableId": "5KwHMmZppj-7TjRC_xQ54",
+ "name": "serviceinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 80,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "AKpf8UbHiwRJll36PQR6f": {
+ "id": "AKpf8UbHiwRJll36PQR6f",
+ "tableId": "5KwHMmZppj-7TjRC_xQ54",
+ "name": "softwareinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 89,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "8agbo_j1bQNrN8OoG2TAs": {
+ "id": "8agbo_j1bQNrN8OoG2TAs",
+ "tableId": "5KwHMmZppj-7TjRC_xQ54",
+ "name": "payment_type",
+ "comment": "",
+ "dataType": "ENUM(onetime,month,free)",
+ "default": "month",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 77,
+ "widthComment": 60,
+ "widthDataType": 148,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "oZ3RpNTiLRp6utYj02FOu": {
+ "id": "oZ3RpNTiLRp6utYj02FOu",
+ "tableId": "5KwHMmZppj-7TjRC_xQ54",
+ "name": "amount",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "bz9oU8libaYKgvU2IR82Y": {
+ "id": "bz9oU8libaYKgvU2IR82Y",
+ "tableId": "5KwHMmZppj-7TjRC_xQ54",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "3v3JWUBHg3mAb4HmHPUP-": {
+ "id": "3v3JWUBHg3mAb4HmHPUP-",
+ "tableId": "5KwHMmZppj-7TjRC_xQ54",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "N9whwkJk3imEwSl_tqk7W": {
+ "id": "N9whwkJk3imEwSl_tqk7W",
+ "tableId": "3tdV9J9ns8BWCGQeCXITI",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "jw-RY9uJDPlANghUUPnJ4": {
+ "id": "jw-RY9uJDPlANghUUPnJ4",
+ "tableId": "3tdV9J9ns8BWCGQeCXITI",
+ "name": "serviceinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 80,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "fdfaSp8HaDoxD96LL1tX4": {
+ "id": "fdfaSp8HaDoxD96LL1tX4",
+ "tableId": "3tdV9J9ns8BWCGQeCXITI",
+ "name": "ipinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "6TYzDwJbiYyvcj6NuvaBI": {
+ "id": "6TYzDwJbiYyvcj6NuvaBI",
+ "tableId": "3tdV9J9ns8BWCGQeCXITI",
+ "name": "payment_type",
+ "comment": "",
+ "dataType": "ENUM(onetime,month,free)",
+ "default": "month",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 77,
+ "widthComment": 60,
+ "widthDataType": 148,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "zk7fke88oHwR5W_3ReKcp": {
+ "id": "zk7fke88oHwR5W_3ReKcp",
+ "tableId": "3tdV9J9ns8BWCGQeCXITI",
+ "name": "amount",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "kTwnu5ylJ22aQ7cBwn3pZ": {
+ "id": "kTwnu5ylJ22aQ7cBwn3pZ",
+ "tableId": "3tdV9J9ns8BWCGQeCXITI",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "PQWVHSFO2ixiAvG2FPtNK": {
+ "id": "PQWVHSFO2ixiAvG2FPtNK",
+ "tableId": "F82-EcEv3fB4uzGzPrPla",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "fDS7QeP4XnANQE_qEtGsY": {
+ "id": "fDS7QeP4XnANQE_qEtGsY",
+ "tableId": "F82-EcEv3fB4uzGzPrPla",
+ "name": "serviceinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 80,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "dDoAacc03mr5Qr0bIwlN6": {
+ "id": "dDoAacc03mr5Qr0bIwlN6",
+ "tableId": "F82-EcEv3fB4uzGzPrPla",
+ "name": "lineinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 63,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "nAYYL4VvZwFBqqY9J5A1P": {
+ "id": "nAYYL4VvZwFBqqY9J5A1P",
+ "tableId": "F82-EcEv3fB4uzGzPrPla",
+ "name": "payment_type",
+ "comment": "",
+ "dataType": "ENUM(onetime,month,free)",
+ "default": "month",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 77,
+ "widthComment": 60,
+ "widthDataType": 148,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "bfvSqmZKRGwglKHwbLVTz": {
+ "id": "bfvSqmZKRGwglKHwbLVTz",
+ "tableId": "F82-EcEv3fB4uzGzPrPla",
+ "name": "amount",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "iYSERwWFGJgDi9-uEJfTS": {
+ "id": "iYSERwWFGJgDi9-uEJfTS",
+ "tableId": "F82-EcEv3fB4uzGzPrPla",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764139,
+ "createAt": 1745819764139
+ }
+ },
+ "Boi84IhuWPlgjwsHQoE7r": {
+ "id": "Boi84IhuWPlgjwsHQoE7r",
+ "tableId": "ttG9p8-yD8NJ7mGo3WNmM",
+ "name": "포인트",
+ "comment": "",
+ "dataType": "",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764140,
+ "createAt": 1745819764140
+ }
+ },
+ "0WQYrtV8bOVl-ZWieHr_Q": {
+ "id": "0WQYrtV8bOVl-ZWieHr_Q",
+ "tableId": "ttG9p8-yD8NJ7mGo3WNmM",
+ "name": "도메인",
+ "comment": "",
+ "dataType": "",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764140,
+ "createAt": 1745819764140
+ }
+ },
+ "9gNKhuq9UnDKyb9KuZ7cY": {
+ "id": "9gNKhuq9UnDKyb9KuZ7cY",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764140,
+ "createAt": 1745819764140
+ }
+ },
+ "O_MKuQKv7yP-k0dsyszkk": {
+ "id": "O_MKuQKv7yP-k0dsyszkk",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "clientinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 73,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "G3KLXJzl6S28Y8pN8hfy2": {
+ "id": "G3KLXJzl6S28Y8pN8hfy2",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "type",
+ "comment": "",
+ "dataType": "ENUM(domain,point)",
+ "default": "point",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 114,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "ALRvTZjYrv4K1ltFn30Mn": {
+ "id": "ALRvTZjYrv4K1ltFn30Mn",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "title",
+ "comment": "",
+ "dataType": "VARCHAR(100)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "3EFy0j6PlKaha31ajJSsZ": {
+ "id": "3EFy0j6PlKaha31ajJSsZ",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "value",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "6ZDZLrSpVuAeUGWvifvq7": {
+ "id": "6ZDZLrSpVuAeUGWvifvq7",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "note",
+ "comment": "",
+ "dataType": "TEXT",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "1bvped58WqdQLlaObX0AT": {
+ "id": "1bvped58WqdQLlaObX0AT",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(use,expired)",
+ "default": "use",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 103,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "iMCMcnQGafCPo46R91hOK": {
+ "id": "iMCMcnQGafCPo46R91hOK",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "6WOE-jsg5rFe2X04atr-Y": {
+ "id": "6WOE-jsg5rFe2X04atr-Y",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "J_xS3cvULouXCTo5gCiTm": {
+ "id": "J_xS3cvULouXCTo5gCiTm",
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 3,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "DGl10GI58QwOHwpTu4Z1Y": {
+ "id": "DGl10GI58QwOHwpTu4Z1Y",
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "userinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 67,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "yMcXBh6lgSLWfLhrivvuX": {
+ "id": "yMcXBh6lgSLWfLhrivvuX",
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "type",
+ "comment": "",
+ "dataType": "ENUM(info,warn,error,debug)",
+ "default": "info",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 157,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "KvMMNu-PKESt0_2K4AJcB": {
+ "id": "KvMMNu-PKESt0_2K4AJcB",
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "class",
+ "comment": "",
+ "dataType": "VARCHAR(255)",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "kKI4hKELPs9Nh5UtQvSu7": {
+ "id": "kKI4hKELPs9Nh5UtQvSu7",
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "method",
+ "comment": "",
+ "dataType": "VARCHAR(255)",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "wkjLZ-Bc9g3Z6Rh_IQ7_q": {
+ "id": "wkjLZ-Bc9g3Z6Rh_IQ7_q",
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "title",
+ "comment": "",
+ "dataType": "VARCHAR(255)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 81,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "W5bxFTggHoO9_PPsfy2EB": {
+ "id": "W5bxFTggHoO9_PPsfy2EB",
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "description",
+ "comment": "",
+ "dataType": "TEXT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 61,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "Wef1cEik-NFTr_alGxLpa": {
+ "id": "Wef1cEik-NFTr_alGxLpa",
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(use,archived)",
+ "default": "use",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 109,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "OgECIC2AofE3lQIX-nOkM": {
+ "id": "OgECIC2AofE3lQIX-nOkM",
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "qHceMMaFcmVnWPlJ2T4Sg": {
+ "id": "qHceMMaFcmVnWPlJ2T4Sg",
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745819764142,
+ "createAt": 1745819764142
+ }
+ },
+ "SFj3q5xg6pcI4RSDKPSgI": {
+ "id": "SFj3q5xg6pcI4RSDKPSgI",
+ "tableId": "ZLEpY5EjuZV21718zf-Y1",
+ "name": "ip_address",
+ "comment": "",
+ "dataType": "VARCHAR(50)",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 75,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821096466,
+ "createAt": 1745820616316
+ }
+ },
+ "Id0h8QbOdlhPj9P1zTm5o": {
+ "id": "Id0h8QbOdlhPj9P1zTm5o",
+ "tableId": "ZLEpY5EjuZV21718zf-Y1",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745820679282,
+ "createAt": 1745820637059
+ }
+ },
+ "Vm1-FnoJLcJ0GRnTp0vnn": {
+ "id": "Vm1-FnoJLcJ0GRnTp0vnn",
+ "tableId": "ZLEpY5EjuZV21718zf-Y1",
+ "name": "updated_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 62,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745820683602,
+ "createAt": 1745820683602
+ }
+ },
+ "R-UjmO-S2UeQdddVNwH5M": {
+ "id": "R-UjmO-S2UeQdddVNwH5M",
+ "tableId": "ZLEpY5EjuZV21718zf-Y1",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745820683602,
+ "createAt": 1745820683602
+ }
+ },
+ "0VUfC7ZS0NaIcqAhZYWHV": {
+ "id": "0VUfC7ZS0NaIcqAhZYWHV",
+ "tableId": "h2unshrMDFXEq0pYzkkj1",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745820775209,
+ "createAt": 1745820772690
+ }
+ },
+ "eh3Ubc6NIftsQEbE3kq-v": {
+ "id": "eh3Ubc6NIftsQEbE3kq-v",
+ "tableId": "h2unshrMDFXEq0pYzkkj1",
+ "name": "deviceinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 78,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745820880560,
+ "createAt": 1745820865007
+ }
+ },
+ "QZeSgl1hDBgLHn8iG-S4s": {
+ "id": "QZeSgl1hDBgLHn8iG-S4s",
+ "tableId": "h2unshrMDFXEq0pYzkkj1",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745820895091,
+ "createAt": 1745820895091
+ }
+ },
+ "-VaB7wQ3cQKai3peK85u8": {
+ "id": "-VaB7wQ3cQKai3peK85u8",
+ "tableId": "h2unshrMDFXEq0pYzkkj1",
+ "name": "ipinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745820925711,
+ "createAt": 1745820911647
+ }
+ },
+ "0ONL4QLQRyZ32MBJ7TN7u": {
+ "id": "0ONL4QLQRyZ32MBJ7TN7u",
+ "tableId": "ZLEpY5EjuZV21718zf-Y1",
+ "name": "price",
+ "comment": "",
+ "dataType": "INT",
+ "default": "0",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821113011,
+ "createAt": 1745821113011
+ }
+ },
+ "e6eWKMFnpXI-rPJZ_9tBt": {
+ "id": "e6eWKMFnpXI-rPJZ_9tBt",
+ "tableId": "ZLEpY5EjuZV21718zf-Y1",
+ "name": "status",
+ "comment": "",
+ "dataType": "ENUM(use,stop)",
+ "default": "use",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 87,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821113012,
+ "createAt": 1745821113011
+ }
+ },
+ "Dw-euSUHCJjMfHmPx9RZw": {
+ "id": "Dw-euSUHCJjMfHmPx9RZw",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "startdate_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 64,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821287277,
+ "createAt": 1745821287276
+ }
+ },
+ "bc7vXbPHzkkSjtIb1Eu_C": {
+ "id": "bc7vXbPHzkkSjtIb1Eu_C",
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "name": "enddate_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "",
+ "options": 0,
+ "ui": {
+ "keys": 0,
+ "widthName": 61,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821287277,
+ "createAt": 1745821287277
+ }
+ },
+ "CvM2-6iCwD-tglwbA0p2z": {
+ "id": "CvM2-6iCwD-tglwbA0p2z",
+ "tableId": "jpnFKaKfog9MCHpL2kUNA",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821382946,
+ "createAt": 1745821381013
+ }
+ },
+ "DvgoXrAW5NmmXkGGuGg3V": {
+ "id": "DvgoXrAW5NmmXkGGuGg3V",
+ "tableId": "jpnFKaKfog9MCHpL2kUNA",
+ "name": "clientinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 73,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821407264,
+ "createAt": 1745821399265
+ }
+ },
+ "SHZNfw8iUBzUTew32Qrz6": {
+ "id": "SHZNfw8iUBzUTew32Qrz6",
+ "tableId": "jpnFKaKfog9MCHpL2kUNA",
+ "name": "evnetinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 73,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821427022,
+ "createAt": 1745821416069
+ }
+ },
+ "UAJUB0FsrZBOtW3odCNDB": {
+ "id": "UAJUB0FsrZBOtW3odCNDB",
+ "tableId": "ttG9p8-yD8NJ7mGo3WNmM",
+ "name": "uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 10,
+ "ui": {
+ "keys": 1,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821492106,
+ "createAt": 1745821489011
+ }
+ },
+ "JNQ2Viala7U_sALVNvoTK": {
+ "id": "JNQ2Viala7U_sALVNvoTK",
+ "tableId": "ttG9p8-yD8NJ7mGo3WNmM",
+ "name": "serviceinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 80,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821537072,
+ "createAt": 1745821529374
+ }
+ },
+ "gn7SpOC02ZjT2ajroWZ9Z": {
+ "id": "gn7SpOC02ZjT2ajroWZ9Z",
+ "tableId": "ttG9p8-yD8NJ7mGo3WNmM",
+ "name": "rackinfo_uid",
+ "comment": "",
+ "dataType": "INT",
+ "default": "",
+ "options": 8,
+ "ui": {
+ "keys": 2,
+ "widthName": 66,
+ "widthComment": 60,
+ "widthDataType": 60,
+ "widthDefault": 60
+ },
+ "meta": {
+ "updateAt": 1745821553006,
+ "createAt": 1745821546632
+ }
+ },
+ "9_FF-LiFPrIftEIMWXpN8": {
+ "id": "9_FF-LiFPrIftEIMWXpN8",
+ "tableId": "ttG9p8-yD8NJ7mGo3WNmM",
+ "name": "created_at",
+ "comment": "",
+ "dataType": "TIMESTAMP",
+ "default": "CURRENT_TIMESTAMP",
+ "options": 8,
+ "ui": {
+ "keys": 0,
+ "widthName": 60,
+ "widthComment": 60,
+ "widthDataType": 65,
+ "widthDefault": 122
+ },
+ "meta": {
+ "updateAt": 1745821562182,
+ "createAt": 1745821562181
+ }
+ }
+ },
+ "relationshipEntities": {
+ "gAVYXWnBSnCw-0ieO4Mil": {
+ "id": "gAVYXWnBSnCw-0ieO4Mil",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "columnIds": [
+ "_AcWUYKzNJd-V0fRHq8Cx"
+ ],
+ "x": 646.5947,
+ "y": 332.6356,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "GDEF0_WuOpaYtsZxjn2zM",
+ "columnIds": [
+ "nPYun5WHoy8uroXUBiqh8"
+ ],
+ "x": 1155.3674,
+ "y": 613.8964000000001,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "lVmT5NRPuRWiB5-mz3uij": {
+ "id": "lVmT5NRPuRWiB5-mz3uij",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "columnIds": [
+ "F9EPb6nsDx6Tf3GG8rvP1"
+ ],
+ "x": 2722.4777,
+ "y": 1898.118,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "IhXnqMFBU_GmCvISNyaKj",
+ "columnIds": [
+ "pDILaJt_-vUo0fH_c6t2O"
+ ],
+ "x": 3153.684,
+ "y": 2313.345233333333,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "yTGnmFSvbFdY5rWfr010W": {
+ "id": "yTGnmFSvbFdY5rWfr010W",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "columnIds": [
+ "7fB6MgwIX6jMpD6d2Sq7k"
+ ],
+ "x": 2721.5265,
+ "y": 2198.658,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "IhXnqMFBU_GmCvISNyaKj",
+ "columnIds": [
+ "5kPFX9LYoh8iinAEpt3lm"
+ ],
+ "x": 3153.684,
+ "y": 2372.0118999999995,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "_WFB7yKFt3jKQzWvZ875h": {
+ "id": "_WFB7yKFt3jKQzWvZ875h",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 1,
+ "start": {
+ "tableId": "ZMGIWLFEswObjH2Sx0NlW",
+ "columnIds": [
+ "2HB01q46-mugMjuOz85YG"
+ ],
+ "x": 2692.3056,
+ "y": 2615.7422,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "IhXnqMFBU_GmCvISNyaKj",
+ "columnIds": [
+ "wQnQfh-JJI9BSQGaahMFu"
+ ],
+ "x": 3153.684,
+ "y": 2430.678566666666,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "WXDbQNvgLU6e2AEqZl8If": {
+ "id": "WXDbQNvgLU6e2AEqZl8If",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "columnIds": [
+ "_AcWUYKzNJd-V0fRHq8Cx"
+ ],
+ "x": 394.5947,
+ "y": 406.6356,
+ "direction": 8
+ },
+ "end": {
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "columnIds": [
+ "sscrxOdwLlx94tx1j_MrH"
+ ],
+ "x": 453.42719999999997,
+ "y": 824.4514,
+ "direction": 4
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "5zrgwg2t7XYDC0zIz0ORc": {
+ "id": "5zrgwg2t7XYDC0zIz0ORc",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "columnIds": [
+ "nb5CGzskl3_LIRA0yyede"
+ ],
+ "x": 311.17719999999997,
+ "y": 1144.4514,
+ "direction": 8
+ },
+ "end": {
+ "tableId": "R4reSshLxH3DQW6fUfSPa",
+ "columnIds": [
+ "bEnLVhafLMHZluEaYba4n"
+ ],
+ "x": 429.6454,
+ "y": 1748.7777,
+ "direction": 4
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "aKIaANWEYltTtJffBo7DN": {
+ "id": "aKIaANWEYltTtJffBo7DN",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "columnIds": [
+ "nb5CGzskl3_LIRA0yyede"
+ ],
+ "x": 737.9272,
+ "y": 1048.4514,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "sgFc3Tg9sWiMm4hsEwKm9",
+ "columnIds": [
+ "zG8_7CN0n4heTPXcS1V8e"
+ ],
+ "x": 1168.3116,
+ "y": 1691.8223,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "2fpNq-aTVMnLCnp481j3_": {
+ "id": "2fpNq-aTVMnLCnp481j3_",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "columnIds": [
+ "F9EPb6nsDx6Tf3GG8rvP1"
+ ],
+ "x": 2192.4777,
+ "y": 1824.118,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "sgFc3Tg9sWiMm4hsEwKm9",
+ "columnIds": [
+ "uNqzMzAALwe_V_QA41OFW"
+ ],
+ "x": 1703.3116,
+ "y": 1691.8223,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "-ehlfECxBa5BFIg6kMnvT": {
+ "id": "-ehlfECxBa5BFIg6kMnvT",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "columnIds": [
+ "nb5CGzskl3_LIRA0yyede"
+ ],
+ "x": 737.9272,
+ "y": 1112.4514,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "aKTVtMqmPG7TJIsmxeVts",
+ "columnIds": [
+ "ywDnRAz0bbHUceo-EmLgZ"
+ ],
+ "x": 1175.8911,
+ "y": 1989.6665,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "DpudF8iv_Pam0hOxkXJHx": {
+ "id": "DpudF8iv_Pam0hOxkXJHx",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "z6WAw_7GnarTYkag4Lcsg",
+ "columnIds": [
+ "7fB6MgwIX6jMpD6d2Sq7k"
+ ],
+ "x": 2204.5265,
+ "y": 2198.658,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "aKTVtMqmPG7TJIsmxeVts",
+ "columnIds": [
+ "sn3p4UDiY4D9XJS4odHbP"
+ ],
+ "x": 1729.8911,
+ "y": 1989.6665,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "qrtpEEOK_OBwxcghJlb0I": {
+ "id": "qrtpEEOK_OBwxcghJlb0I",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "columnIds": [
+ "nb5CGzskl3_LIRA0yyede"
+ ],
+ "x": 595.6772,
+ "y": 1144.4514,
+ "direction": 8
+ },
+ "end": {
+ "tableId": "5KwHMmZppj-7TjRC_xQ54",
+ "columnIds": [
+ "CgGKx59wNvLpWcoBrEuvK"
+ ],
+ "x": 1173.8733,
+ "y": 2295.3876,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "fXVyT3pzVo0T0fgoFz4Gi": {
+ "id": "fXVyT3pzVo0T0fgoFz4Gi",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "ZMGIWLFEswObjH2Sx0NlW",
+ "columnIds": [
+ "2HB01q46-mugMjuOz85YG"
+ ],
+ "x": 2206.3056,
+ "y": 2615.7422,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "5KwHMmZppj-7TjRC_xQ54",
+ "columnIds": [
+ "AKpf8UbHiwRJll36PQR6f"
+ ],
+ "x": 1717.8733,
+ "y": 2295.3876,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "gNCG3TpxxGPRdE0xNJM4Z": {
+ "id": "gNCG3TpxxGPRdE0xNJM4Z",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "columnIds": [
+ "nb5CGzskl3_LIRA0yyede"
+ ],
+ "x": 737.9272,
+ "y": 984.4514,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "3tdV9J9ns8BWCGQeCXITI",
+ "columnIds": [
+ "jw-RY9uJDPlANghUUPnJ4"
+ ],
+ "x": 1170.1167,
+ "y": 1393.2643,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "KG3dDhcIJxHk3e1aGPWPa": {
+ "id": "KG3dDhcIJxHk3e1aGPWPa",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "ZLEpY5EjuZV21718zf-Y1",
+ "columnIds": [],
+ "x": 2191.6819,
+ "y": 1524.3551,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "3tdV9J9ns8BWCGQeCXITI",
+ "columnIds": [
+ "fdfaSp8HaDoxD96LL1tX4"
+ ],
+ "x": 1705.1167,
+ "y": 1393.2643,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "AckV69XB9r2m1VHP64gtd": {
+ "id": "AckV69XB9r2m1VHP64gtd",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "columnIds": [
+ "nb5CGzskl3_LIRA0yyede"
+ ],
+ "x": 737.9272,
+ "y": 920.4514,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "F82-EcEv3fB4uzGzPrPla",
+ "columnIds": [
+ "fDS7QeP4XnANQE_qEtGsY"
+ ],
+ "x": 1168.4571,
+ "y": 1120.2791,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "6blFGW77QEOBG_yDwtFQq": {
+ "id": "6blFGW77QEOBG_yDwtFQq",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "doERb3lIVeBW_D0NtNYX8",
+ "columnIds": [
+ "7B0zaLoZnOoMNW8OHZlrQ"
+ ],
+ "x": 2182.9774,
+ "y": 1237.0705,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "F82-EcEv3fB4uzGzPrPla",
+ "columnIds": [
+ "dDoAacc03mr5Qr0bIwlN6"
+ ],
+ "x": 1703.4571,
+ "y": 1120.2791,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "MG6gqLi-aVImpunYixNMB": {
+ "id": "MG6gqLi-aVImpunYixNMB",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "columnIds": [
+ "_AcWUYKzNJd-V0fRHq8Cx"
+ ],
+ "x": 646.5947,
+ "y": 184.6356,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "columnIds": [
+ "O_MKuQKv7yP-k0dsyszkk"
+ ],
+ "x": 1128.567,
+ "y": 270.097,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "95uRv2fz3rssNbkyuzkLh": {
+ "id": "95uRv2fz3rssNbkyuzkLh",
+ "identification": false,
+ "relationshipType": 4,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "Jq5Qkun2FzQhCGKANIVOZ",
+ "columnIds": [
+ "mfHtgzc_Aeocr6xkgwYWh"
+ ],
+ "x": 652.7353,
+ "y": 2330.2652,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "gsa0XtQZQgrJ8ZXy8VQVg",
+ "columnIds": [
+ "DGl10GI58QwOHwpTu4Z1Y"
+ ],
+ "x": 1172.834,
+ "y": 2689.8385,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745819764143,
+ "createAt": 1745819764143
+ }
+ },
+ "Aj0_hRYAK-XVJSijPSu07": {
+ "id": "Aj0_hRYAK-XVJSijPSu07",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "columnIds": [
+ "F9EPb6nsDx6Tf3GG8rvP1"
+ ],
+ "x": 2457.4777,
+ "y": 1676.118,
+ "direction": 4
+ },
+ "end": {
+ "tableId": "ZLEpY5EjuZV21718zf-Y1",
+ "columnIds": [
+ "SFj3q5xg6pcI4RSDKPSgI"
+ ],
+ "x": 2416.6819,
+ "y": 1576.3551,
+ "direction": 8
+ },
+ "meta": {
+ "updateAt": 1745820616317,
+ "createAt": 1745820616317
+ }
+ },
+ "Jj6R_4lq6u3hMzLKjgqc9": {
+ "id": "Jj6R_4lq6u3hMzLKjgqc9",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "B4qGh3KZsXHQ3_4EOgwJZ",
+ "columnIds": [
+ "F9EPb6nsDx6Tf3GG8rvP1"
+ ],
+ "x": 2722.4777,
+ "y": 1750.118,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "h2unshrMDFXEq0pYzkkj1",
+ "columnIds": [
+ "eh3Ubc6NIftsQEbE3kq-v"
+ ],
+ "x": 2898.2473,
+ "y": 1695.7166,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745820865008,
+ "createAt": 1745820865008
+ }
+ },
+ "hHtOx5fLjgkRN3SIPGLfw": {
+ "id": "hHtOx5fLjgkRN3SIPGLfw",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "ZLEpY5EjuZV21718zf-Y1",
+ "columnIds": [
+ "Id0h8QbOdlhPj9P1zTm5o"
+ ],
+ "x": 2647.6819,
+ "y": 1524.3551,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "h2unshrMDFXEq0pYzkkj1",
+ "columnIds": [
+ "-VaB7wQ3cQKai3peK85u8"
+ ],
+ "x": 2898.2473,
+ "y": 1619.7166,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745820911647,
+ "createAt": 1745820911647
+ }
+ },
+ "xRge-SlcORE83ke41txTG": {
+ "id": "xRge-SlcORE83ke41txTG",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "6ajvOCaGuXU9pzV0Y9jEi",
+ "columnIds": [
+ "_AcWUYKzNJd-V0fRHq8Cx"
+ ],
+ "x": 646.5947,
+ "y": 184.6356,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "jpnFKaKfog9MCHpL2kUNA",
+ "columnIds": [
+ "DvgoXrAW5NmmXkGGuGg3V"
+ ],
+ "x": 1156.7488,
+ "y": 196.2122,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745821399265,
+ "createAt": 1745821399265
+ }
+ },
+ "6X-W5Q0SxPLd-sIRlwf5W": {
+ "id": "6X-W5Q0SxPLd-sIRlwf5W",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "jO40Ej5EXImXnadoJo9bn",
+ "columnIds": [
+ "9gNKhuq9UnDKyb9KuZ7cY"
+ ],
+ "x": 2162.713,
+ "y": 245.7067,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "jpnFKaKfog9MCHpL2kUNA",
+ "columnIds": [
+ "SHZNfw8iUBzUTew32Qrz6"
+ ],
+ "x": 1534.7488,
+ "y": 196.2122,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1745821416069,
+ "createAt": 1745821416069
+ }
+ },
+ "xoiTCIGWOuuUaMQlCDnhd": {
+ "id": "xoiTCIGWOuuUaMQlCDnhd",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "kc1EFvFhlBSc0B0bDgX28",
+ "columnIds": [
+ "nb5CGzskl3_LIRA0yyede"
+ ],
+ "x": 737.9272,
+ "y": 856.4514,
+ "direction": 2
+ },
+ "end": {
+ "tableId": "ttG9p8-yD8NJ7mGo3WNmM",
+ "columnIds": [
+ "JNQ2Viala7U_sALVNvoTK"
+ ],
+ "x": 1161.9527,
+ "y": 895.519,
+ "direction": 1
+ },
+ "meta": {
+ "updateAt": 1745821529375,
+ "createAt": 1745821529375
+ }
+ },
+ "6DEBBDnRPVRt5gU5AuUcU": {
+ "id": "6DEBBDnRPVRt5gU5AuUcU",
+ "identification": false,
+ "relationshipType": 16,
+ "startRelationshipType": 2,
+ "start": {
+ "tableId": "koyEKtRkVwNF0GzL7x9EJ",
+ "columnIds": [
+ "kfY2wrPM9lt3o_CUkvoak"
+ ],
+ "x": 2182.5045,
+ "y": 832.7384,
+ "direction": 1
+ },
+ "end": {
+ "tableId": "ttG9p8-yD8NJ7mGo3WNmM",
+ "columnIds": [
+ "gn7SpOC02ZjT2ajroWZ9Z"
+ ],
+ "x": 1613.9527,
+ "y": 895.519,
+ "direction": 2
+ },
+ "meta": {
+ "updateAt": 1745821546633,
+ "createAt": 1745821546633
+ }
+ }
+ },
+ "indexEntities": {},
+ "indexColumnEntities": {},
+ "memoEntities": {}
+ },
+ "lww": {
+ "SFj3q5xg6pcI4RSDKPSgI": [
+ "tableColumnEntities",
+ 1745820616314,
+ -1,
+ {
+ "options(notNull)": 1745820616314,
+ "name": 1745821082574,
+ "dataType": 1745821096465,
+ "default": 1745820616314,
+ "comment": 1745820616314
+ }
+ ],
+ "Aj0_hRYAK-XVJSijPSu07": [
+ "relationshipEntities",
+ 1745820616314,
+ 1745821049553,
+ {}
+ ],
+ "Id0h8QbOdlhPj9P1zTm5o": [
+ "tableColumnEntities",
+ 1745820637058,
+ -1,
+ {
+ "name": 1745820637058,
+ "dataType": 1745820637058,
+ "default": 1745820637058,
+ "comment": 1745820637058,
+ "options(notNull)": 1745820637058,
+ "options(unique)": 1745820637058,
+ "options(autoIncrement)": 1745820637058,
+ "options(primaryKey)": 1745820679281
+ }
+ ],
+ "Do7xvuwqsEo1L9PMTYoFg": [
+ "tableColumnEntities",
+ -1,
+ 1745820675465,
+ {
+ "name": 1745820641243
+ }
+ ],
+ "Vm1-FnoJLcJ0GRnTp0vnn": [
+ "tableColumnEntities",
+ 1745820683600,
+ -1,
+ {
+ "name": 1745820683600,
+ "dataType": 1745820683600,
+ "default": 1745820683600,
+ "comment": 1745820683600,
+ "options(notNull)": 1745820683600,
+ "options(unique)": 1745820683600,
+ "options(autoIncrement)": 1745820683600
+ }
+ ],
+ "R-UjmO-S2UeQdddVNwH5M": [
+ "tableColumnEntities",
+ 1745820683600,
+ -1,
+ {
+ "name": 1745820683600,
+ "dataType": 1745820683600,
+ "default": 1745820683600,
+ "comment": 1745820683600,
+ "options(notNull)": 1745820683600,
+ "options(unique)": 1745820683600,
+ "options(autoIncrement)": 1745820683600
+ }
+ ],
+ "h2unshrMDFXEq0pYzkkj1": [
+ "tableEntities",
+ 1745820710361,
+ -1,
+ {
+ "name": 1745820996973,
+ "comment": 1745820759182
+ }
+ ],
+ "0VUfC7ZS0NaIcqAhZYWHV": [
+ "tableColumnEntities",
+ 1745820772689,
+ -1,
+ {
+ "name": 1745820772689,
+ "dataType": 1745820772689,
+ "default": 1745820772689,
+ "comment": 1745820772689,
+ "options(notNull)": 1745820772689,
+ "options(unique)": 1745820772689,
+ "options(autoIncrement)": 1745820772689,
+ "options(primaryKey)": 1745820775209
+ }
+ ],
+ "eh3Ubc6NIftsQEbE3kq-v": [
+ "tableColumnEntities",
+ 1745820865006,
+ -1,
+ {
+ "options(notNull)": 1745820865006,
+ "name": 1745820880560,
+ "dataType": 1745820865006,
+ "default": 1745820865006,
+ "comment": 1745820865006
+ }
+ ],
+ "Jj6R_4lq6u3hMzLKjgqc9": [
+ "relationshipEntities",
+ 1745820865006,
+ -1,
+ {}
+ ],
+ "QZeSgl1hDBgLHn8iG-S4s": [
+ "tableColumnEntities",
+ 1745820895090,
+ -1,
+ {
+ "name": 1745820895090,
+ "dataType": 1745820895090,
+ "default": 1745820895090,
+ "comment": 1745820895090,
+ "options(notNull)": 1745820895090,
+ "options(unique)": 1745820895090,
+ "options(autoIncrement)": 1745820895090
+ }
+ ],
+ "-VaB7wQ3cQKai3peK85u8": [
+ "tableColumnEntities",
+ 1745820911646,
+ -1,
+ {
+ "options(notNull)": 1745820911646,
+ "name": 1745820925710,
+ "dataType": 1745820911646,
+ "default": 1745820911646,
+ "comment": 1745820911646
+ }
+ ],
+ "hHtOx5fLjgkRN3SIPGLfw": [
+ "relationshipEntities",
+ 1745820911646,
+ -1,
+ {}
+ ],
+ "0ONL4QLQRyZ32MBJ7TN7u": [
+ "tableColumnEntities",
+ 1745821113010,
+ -1,
+ {
+ "name": 1745821113010,
+ "dataType": 1745821113010,
+ "default": 1745821113010,
+ "comment": 1745821113010,
+ "options(notNull)": 1745821113010,
+ "options(unique)": 1745821113010,
+ "options(autoIncrement)": 1745821113010
+ }
+ ],
+ "e6eWKMFnpXI-rPJZ_9tBt": [
+ "tableColumnEntities",
+ 1745821113010,
+ -1,
+ {
+ "name": 1745821113010,
+ "dataType": 1745821113010,
+ "default": 1745821113010,
+ "comment": 1745821113010,
+ "options(notNull)": 1745821113010,
+ "options(unique)": 1745821113010,
+ "options(autoIncrement)": 1745821113010
+ }
+ ],
+ "MG6gqLi-aVImpunYixNMB": [
+ "relationshipEntities",
+ -1,
+ 1745821259113,
+ {}
+ ],
+ "O_MKuQKv7yP-k0dsyszkk": [
+ "tableColumnEntities",
+ -1,
+ 1745821259113,
+ {}
+ ],
+ "3EFy0j6PlKaha31ajJSsZ": [
+ "tableColumnEntities",
+ -1,
+ 1745821272961,
+ {}
+ ],
+ "Dw-euSUHCJjMfHmPx9RZw": [
+ "tableColumnEntities",
+ 1745821287276,
+ -1,
+ {
+ "name": 1745821287276,
+ "dataType": 1745821287276,
+ "default": 1745821287276,
+ "comment": 1745821287276,
+ "options(notNull)": 1745821287276,
+ "options(unique)": 1745821287276,
+ "options(autoIncrement)": 1745821287276
+ }
+ ],
+ "bc7vXbPHzkkSjtIb1Eu_C": [
+ "tableColumnEntities",
+ 1745821287276,
+ -1,
+ {
+ "name": 1745821287276,
+ "dataType": 1745821287276,
+ "default": 1745821287276,
+ "comment": 1745821287276,
+ "options(notNull)": 1745821287276,
+ "options(unique)": 1745821287276,
+ "options(autoIncrement)": 1745821287276
+ }
+ ],
+ "jpnFKaKfog9MCHpL2kUNA": [
+ "tableEntities",
+ 1745821326010,
+ -1,
+ {
+ "name": 1745821342314,
+ "comment": 1745821367230
+ }
+ ],
+ "CvM2-6iCwD-tglwbA0p2z": [
+ "tableColumnEntities",
+ 1745821381012,
+ -1,
+ {
+ "name": 1745821381012,
+ "dataType": 1745821381012,
+ "default": 1745821381012,
+ "comment": 1745821381012,
+ "options(notNull)": 1745821381012,
+ "options(unique)": 1745821381012,
+ "options(autoIncrement)": 1745821381012,
+ "options(primaryKey)": 1745821382945
+ }
+ ],
+ "DvgoXrAW5NmmXkGGuGg3V": [
+ "tableColumnEntities",
+ 1745821399264,
+ -1,
+ {
+ "options(notNull)": 1745821399264,
+ "name": 1745821407263,
+ "dataType": 1745821399264,
+ "default": 1745821399264,
+ "comment": 1745821399264
+ }
+ ],
+ "xRge-SlcORE83ke41txTG": [
+ "relationshipEntities",
+ 1745821399264,
+ -1,
+ {}
+ ],
+ "SHZNfw8iUBzUTew32Qrz6": [
+ "tableColumnEntities",
+ 1745821416068,
+ -1,
+ {
+ "options(notNull)": 1745821416068,
+ "name": 1745821427022,
+ "dataType": 1745821416068,
+ "default": 1745821416068,
+ "comment": 1745821416068
+ }
+ ],
+ "6X-W5Q0SxPLd-sIRlwf5W": [
+ "relationshipEntities",
+ 1745821416068,
+ -1,
+ {}
+ ],
+ "UAJUB0FsrZBOtW3odCNDB": [
+ "tableColumnEntities",
+ 1745821489010,
+ -1,
+ {
+ "name": 1745821489010,
+ "dataType": 1745821489010,
+ "default": 1745821489010,
+ "comment": 1745821489010,
+ "options(notNull)": 1745821489010,
+ "options(unique)": 1745821489010,
+ "options(autoIncrement)": 1745821489010,
+ "options(primaryKey)": 1745821492106
+ }
+ ],
+ "Boi84IhuWPlgjwsHQoE7r": [
+ "tableColumnEntities",
+ -1,
+ 1745821494586,
+ {}
+ ],
+ "0WQYrtV8bOVl-ZWieHr_Q": [
+ "tableColumnEntities",
+ -1,
+ 1745821495593,
+ {}
+ ],
+ "JNQ2Viala7U_sALVNvoTK": [
+ "tableColumnEntities",
+ 1745821529373,
+ -1,
+ {
+ "options(notNull)": 1745821529373,
+ "name": 1745821537071,
+ "dataType": 1745821529373,
+ "default": 1745821529373,
+ "comment": 1745821529373
+ }
+ ],
+ "xoiTCIGWOuuUaMQlCDnhd": [
+ "relationshipEntities",
+ 1745821529373,
+ -1,
+ {}
+ ],
+ "gn7SpOC02ZjT2ajroWZ9Z": [
+ "tableColumnEntities",
+ 1745821546630,
+ -1,
+ {
+ "options(notNull)": 1745821546630,
+ "name": 1745821553006,
+ "dataType": 1745821546630,
+ "default": 1745821546630,
+ "comment": 1745821546630
+ }
+ ],
+ "6DEBBDnRPVRt5gU5AuUcU": [
+ "relationshipEntities",
+ 1745821546630,
+ -1,
+ {}
+ ],
+ "9_FF-LiFPrIftEIMWXpN8": [
+ "tableColumnEntities",
+ 1745821562181,
+ -1,
+ {
+ "name": 1745821562181,
+ "dataType": 1745821562181,
+ "default": 1745821562181,
+ "comment": 1745821562181,
+ "options(notNull)": 1745821562181,
+ "options(unique)": 1745821562181,
+ "options(autoIncrement)": 1745821562181
+ }
+ ],
+ "uBuqi8eabZOjHwaEZ4HnE": [
+ "tableColumnEntities",
+ -1,
+ 1745821576034,
+ {}
+ ],
+ "oslDq1FY5cMlsObGiCwzd": [
+ "tableColumnEntities",
+ -1,
+ 1745821579473,
+ {}
+ ],
+ "bz9oU8libaYKgvU2IR82Y": [
+ "tableColumnEntities",
+ -1,
+ 1745821583978,
+ {}
+ ],
+ "OgECIC2AofE3lQIX-nOkM": [
+ "tableColumnEntities",
+ -1,
+ 1745821593690,
+ {}
+ ],
+ "6JNus18UqajPfwOjkPali": [
+ "tableColumnEntities",
+ -1,
+ -1,
+ {
+ "options(notNull)": 1745821688154
+ }
+ ],
+ "z-q_Ah0sghd0nR7VcCTLX": [
+ "tableColumnEntities",
+ -1,
+ -1,
+ {
+ "options(notNull)": 1745821700049
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/Database/update.txt b/app/Database/update.txt
new file mode 100644
index 0000000..0a4a9d4
--- /dev/null
+++ b/app/Database/update.txt
@@ -0,0 +1,42 @@
+###cloudflareaccount 조정###
+1. 필드추가
+alter table cloudflareaccount add column id varchar(30) not null after auth_uid;
+alter table cloudflareaccount add column authkey varchar(255) not null after id;
+alter table cloudflareaccount add column oldkey varchar(255) null after authkey;
+
+2. 내용복사
+update cloudflareaccount
+join cloudflareauth on cloudflareaccount.auth_uid = cloudflareauth.uid
+set cloudflareaccount.id=cloudflareauth.id,
+cloudflareaccount.authkey=cloudflareauth.authkey,
+cloudflareaccount.oldkey=cloudflareauth.oldkey
+
+3. foreign key 삭제 , index key 삭제
+show create table cloudflareaccount;
+ALTER TABLE cloudflareaccount DROP FOREIGN KEY cloudflareaccount_ibfk_1;
+ALTER TABLE cloudflareaccount DROP KEY cloudflareaccount_ibfk_1;
+
+4. auth_uid column 삭제
+show create table cloudflareaccount;
+ALTER TABLE cloudflareaccount DROP column auth_uid;
+
+5. id unique key 추가
+ALTER TABLE cloudflareaccount ADD UNIQUE key cloudflareaccount_ibuk_1 (id);
+ALTER TABLE cloudflareaccount ADD UNIQUE key cloudflareaccount_ibuk_2 (authkey);
+
+6. auditlog용 table추가
+DROP TABLE cloudflareauditlog;
+CREATE TABLE cloudflareauditlog (
+ uid varchar(255) NOT NULL COMMENT 'id',
+ action varchar(100) NOT NULL COMMENT 'action->type',
+ action_info varchar(255) NULL COMMENT 'action->info',
+ actor varchar(100) NOT NULL COMMENT 'actor->type',
+ interface varchar(100) NULL COMMENT 'interface',
+ zone_name varchar(100) NULL COMMENT 'newValueJson->zone_name',
+ meta text NULL COMMENT 'meta domain settings info',
+ resource text NULL COMMENT 'newValueJson',
+ status varchar(10) NOT NULL COMMENT 'action->result',
+ updated_at timestamp NULL DEFAULT NULL,
+ created_at timestamp NOT NULL COMMENT 'when',
+ PRIMARY KEY (uid)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='cloudflare Auditlog 정보';
\ No newline at end of file
diff --git a/app/Entities/CommonEntity.php b/app/Entities/CommonEntity.php
new file mode 100644
index 0000000..f24a617
--- /dev/null
+++ b/app/Entities/CommonEntity.php
@@ -0,0 +1,38 @@
+$field;
+ }
+ final public function getTitle(): string
+ {
+ $field = constant("static::TitleField");
+ return $this->$field;
+ }
+ final public function getUpdatedAt(): string
+ {
+ return $this->created_at;
+ }
+ final public function getCreatedAt(): string
+ {
+ return $this->created_at;
+ }
+ //공통부분
+}
diff --git a/app/Entities/MyLogEntity.php b/app/Entities/MyLogEntity.php
new file mode 100644
index 0000000..dd56bef
--- /dev/null
+++ b/app/Entities/MyLogEntity.php
@@ -0,0 +1,18 @@
+getPK()}:{$this->getTitle()}}";
+ }
+ //공통부분
+ //Common Function
+}
diff --git a/app/Entities/UserEntity.php b/app/Entities/UserEntity.php
new file mode 100644
index 0000000..e0980ce
--- /dev/null
+++ b/app/Entities/UserEntity.php
@@ -0,0 +1,24 @@
+getPK()}:{$this->getID()}:{$this->getTitle()},{$this->getRole()}}";
+ }
+ //공통부분
+
+ public function getID(): string
+ {
+ return $this->attributes['id'];
+ }
+ public function getRole(): string
+ {
+ return $this->attributes['role'];
+ }
+}
diff --git a/app/Entities/UserSNSEntity.php b/app/Entities/UserSNSEntity.php
new file mode 100644
index 0000000..69f99ba
--- /dev/null
+++ b/app/Entities/UserSNSEntity.php
@@ -0,0 +1,43 @@
+getPK()}|{$this->getID()}|{$this->getTitle()}";
+ }
+ public function getPK(): int
+ {
+ return $this->attributes[UserSNSModel::PK];
+ }
+ public function getTitle(): string
+ {
+ return $this->attributes[UserSNSModel::TITLE];
+ }
+ public function setTitle(string $title): void
+ {
+ $this->attributes[UserSNSModel::TITLE] = $title;
+ }
+ //Common Function
+ public function getParent(): int|null
+ {
+ return $this->attributes[UserSNSModel::PARENT];
+ }
+ public function getID(): string
+ {
+ return $this->attributes['id'];
+ }
+ public function getSite(): string
+ {
+ return $this->attributes['site'];
+ }
+ public function getEmail(): string
+ {
+ return $this->attributes['email'];
+ }
+}
diff --git a/app/Filters/.gitkeep b/app/Filters/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/app/Filters/AuthFilter.php b/app/Filters/AuthFilter.php
new file mode 100644
index 0000000..0e70f1e
--- /dev/null
+++ b/app/Filters/AuthFilter.php
@@ -0,0 +1,62 @@
+isLoggedIn()) {
+ $auth->pushCurrentUrl($request->getUri()->getPath() . ($request->getUri()->getQuery() ? "?" . $request->getUri()->getQuery() : ""));
+ return redirect()->to(URLS['LOGIN'])->with('error', '로그인을하셔야합니다.');
+ }
+ //User Role 비교 // 회원 ROLES이 필요ROLE($arguments) 목록에 존재하지 않으면(ACL)
+ if (!$auth->isAccessRole($arguments)) {
+ // dd($auth->popPreviousUrl());
+ return redirect()->back()->with(
+ 'error',
+ "회원[{$auth->getNameByAuthInfo()}]님은 접속에 필요한 권한이 없습니다. "
+ );
+ }
+ }
+
+ /**
+ * Allows After filters to inspect and modify the response
+ * object as needed. This method does not allow any way
+ * to stop execution of other after filters, short of
+ * throwing an Exception or Error.
+ *
+ * @param RequestInterface $request
+ * @param ResponseInterface $response
+ * @param array|null $arguments
+ *
+ * @return mixed
+ */
+ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
+ {
+ //
+ }
+}
diff --git a/app/Helpers/.gitkeep b/app/Helpers/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/app/Helpers/CommonHelper.php b/app/Helpers/CommonHelper.php
new file mode 100644
index 0000000..230a99c
--- /dev/null
+++ b/app/Helpers/CommonHelper.php
@@ -0,0 +1,298 @@
+getRandomString($length, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_-=+;:,.?");
+ }
+
+ // byte값을 알아보기 쉽게 변환
+ final public function getSizeForHuman($bytes)
+ {
+ $ext = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
+ $unitCount = 0;
+ for (; $bytes > 1024; $unitCount++) {
+ $bytes /= 1024;
+ }
+ return floor($bytes) . $ext[$unitCount];
+ }
+
+ // Proxy등을 통하여 Client_IP가 알수없는경우 실제사용자의 IP를 가져오기 위한것
+ final public function getClientIP($clientIP = false)
+ {
+ if (isset($_SERVER['HTTP_CLIENT_IP'])) {
+ $clientIP = $_SERVER['HTTP_CLIENT_IP'];
+ } else if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ $clientIP = $_SERVER['HTTP_X_FORWARDED_FOR'];
+ } else if (isset($_SERVER['HTTP_X_FORWARDED'])) {
+ $clientIP = $_SERVER['HTTP_X_FORWARDED'];
+ } else if (isset($_SERVER['HTTP_FORWARDED_FOR'])) {
+ $clientIP = $_SERVER['HTTP_FORWARDED_FOR'];
+ } else if (isset($_SERVER['HTTP_FORWARDED'])) {
+ $clientIP = $_SERVER['HTTP_FORWARDED'];
+ } else if (isset($_SERVER['REMOTE_ADDR'])) {
+ $clientIP = $_SERVER['REMOTE_ADDR'];
+ }
+ return $clientIP;
+ }
+
+ final public function isDomain(string $domain): bool
+ {
+ $pattern_validation = '/((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z0-9\&\.\/\?\:@\-_=#])*/';
+ return preg_match($pattern_validation, $domain);
+ }
+
+ final public function isIPAddress(string $ip, $type = false): bool
+ {
+ switch ($type) {
+ case 'ipv4':
+ $result = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
+ break;
+ case 'ipv6':
+ $result = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
+ break;
+ case 'all':
+ $result = filter_var($ip, FILTER_VALIDATE_IP);
+ break;
+ default:
+ $result = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
+ break;
+ }
+ return $result;
+ }
+
+ final public function isHost(string $host): bool
+ {
+ $pattern_validation = '/[a-zA-Z0-9\.\/\?\:@\*\-_=#]/';
+ return preg_match($pattern_validation, $host);
+ }
+
+ // (EX:192.168.1.0 -> 192.168.001.000)
+ final public function convertIPV4toCIDR($cidr)
+ {
+ $temps = explode(".", $cidr);
+ return sprintf("%03d.%03d.%03d.%03d", $temps[0], $temps[1], $temps[2], $temps[3]);
+ }
+
+ // (EX:192.168.001.0000 -> 192.168.1.0)
+ final public function convertCIDRtoIPV4($ipv4)
+ {
+ $temps = explode(".", $ipv4);
+ return sprintf("%d.%d.%d.%d", $temps[0], $temps[1], $temps[2], $temps[3]);
+ }
+
+ final public function isMobile()
+ {
+ // Check the server headers to see if they're mobile friendly
+ if (isset($_SERVER["HTTP_X_WAP_PROFILE"])) {
+ return true;
+ }
+ // If the http_accept header supports wap then it's a mobile too
+ if (preg_match("/wap\.|\.wap/i", $_SERVER["HTTP_ACCEPT"])) {
+ return true;
+ }
+ // Still no luck? Let's have a look at the user agent on the browser. If it contains
+ // any of the following, it's probably a mobile device. Kappow!
+ if (isset($_SERVER["HTTP_USER_AGENT"])) {
+ $user_agents = array("midp", "j2me", "avantg", "docomo", "novarra", "palmos", "palmsource", "240x320", "opwv", "chtml", "pda", "windows\ ce", "mmp\/", "blackberry", "mib\/", "symbian", "wireless", "nokia", "hand", "mobi", "phone", "cdm", "up\.b", "audio", "SIE\-", "SEC\-", "samsung", "HTC", "mot\-", "mitsu", "sagem", "sony", "alcatel", "lg", "erics", "vx", "NEC", "philips", "mmm", "xx", "panasonic", "sharp", "wap", "sch", "rover", "pocket", "benq", "java", "pt", "pg", "vox", "amoi", "bird", "compal", "kg", "voda", "sany", "kdd", "dbt", "sendo", "sgh", "gradi", "jb", "\d\d\di", "moto");
+ foreach ($user_agents as $user_string) {
+ if (preg_match("/" . $user_string . "/i", $_SERVER["HTTP_USER_AGENT"])) {
+ return true;
+ }
+ }
+ }
+ // Let's NOT return "mobile" if it's an iPhone, because the iPhone can render normal pages quite well.
+ if (preg_match("/iphone/i", $_SERVER["HTTP_USER_AGENT"])) {
+ return false;
+ }
+ // None of the above? Then it's probably not a mobile device.
+ return false;
+ }
+
+ final public function alert(string $msg, $url = null)
+ {
+ $msg = preg_replace("/\r/", "\\r", $msg);
+ $msg = preg_replace("/\n/", "\\n", $msg);
+ $msg = preg_replace("/\'/", "\'", $msg);
+ $msg = preg_replace("/\"/", "\'", $msg);
+ $msg = "alert(\"{$msg}\");";
+ switch ($url) {
+ case 'close':
+ $msg .= "window.close();";
+ break;
+ case 'back':
+ $msg .= "history.back();";
+ break;
+ default:
+ $msg .= $url ? "location.href=\"{$url}\";" : "";
+ break;
+ }
+ return "";
+ }
+
+ public function getFieldLabel(string $field, array $viewDatas, array $extras = []): string
+ {
+ switch ($field) {
+ default:
+ $extras = (strpos($viewDatas['field_rules'][$field], 'required') !== false) ? ["class" => "text-danger", "required" => "", ...$extras] : $extras;
+ $label = form_label(lang("{$viewDatas['class_path']}.label.{$field}"), $field, $extras);
+ break;
+ }
+ return $label;
+ }
+
+ // header.php에서 getFieldForm_Helper사용
+ public function getFieldForm(string $field, mixed $value, array $viewDatas, array $extras = []): string
+ {
+ if (in_array($viewDatas['action'], ['create', 'modify'])) {
+ $extras = (strpos($viewDatas['field_rules'][$field], 'required') !== false) ? ["class" => "form-control", "required" => "", ...$extras] : ["class" => "form-control", ...$extras];
+ }
+ $value = $value ?: DEFAULTS['EMPTY'];
+ switch ($field) {
+ case 'status':
+ $form = form_dropdown($field, [
+ "" => lang($viewDatas['class_path'] . '.label.' . $field) . ' 선택',
+ ] + $viewDatas['field_options'][$field], $value, $extras);
+ break;
+ case 'updated_at':
+ case 'created_at':
+ $extra_class = isset($extras['class']) ? $extras['class'] . ' calender' : 'calender';
+ $form = form_input($field, $value, ['class' => $extra_class, ...array_diff_key($extras, ['class' => ''])]);
+ break;
+ default:
+ $form = form_input($field, $value, ["autocomplete" => $field, ...$extras]);
+ break;
+ }
+ return $form;
+ }
+
+ public function getFieldView(string $field, array $viewDatas, array $extras = []): string
+ {
+ $value = $viewDatas['entity']->$field ?: DEFAULTS['EMPTY'];
+ switch ($field) {
+ case 'category_uid':
+ foreach (array_values($viewDatas['field_options'][$field]) as $category_2depths) {
+ foreach ($category_2depths as $key => $depth) {
+ if ($key == $depth) {
+ $value = $depth;
+ }
+ }
+ }
+ break;
+ case 'updated_at':
+ case 'created_at':
+ $value = $value ? date("Y-m-d", strtotime($value)) : "";
+ break;
+ default:
+ if (in_array($field, $viewDatas['filter_fields'])) {
+ $extras["onChange"] = sprintf(
+ 'location.href="%s/toggle/%s/%s?%s="+this.options[this.selectedIndex].value',
+ current_url(),
+ $viewDatas['entity']->getPK(),
+ $field,
+ $field
+ );
+ $value = $this->getFieldForm($field, $viewDatas['entity']->$field, $viewDatas, $extras);
+ }
+ break;
+ }
+ return $value;
+ }
+
+ public function getListRowColor($entity): string
+ {
+ return $entity->status != DEFAULTS['STATUS'] ? 'class="table-danger"' : "";
+ }
+
+ public function getListLabel(string $field, array $viewDatas, array $extras = []): string
+ {
+ switch ($field) {
+ default:
+ $label = $this->getFieldLabel($field, $viewDatas, $extras);
+ if (isset($viewDatas['order_field']) && $viewDatas['order_field'] == $field) {
+ $label .= $viewDatas['order_value'] == 'ASC' ? ICONS["UP"] : ICONS["DOWN"];
+ }
+ $query = $viewDatas['uri']->getQuery(['except' => ['order_field', 'order_value']]);
+ $query .= empty($query) ? "" : "&";
+ $query .= "order_field={$field}&order_value=";
+ $query .= isset($viewDatas['order_value']) && $viewDatas['order_value'] == 'DESC' ? "ASC" : "DESC";
+ $label = anchor(current_url() . "?" . $query, $label);
+ break;
+ }
+ return $label;
+ }
+
+ public function getListButton(string $action, array $viewDatas, array $extras = []): string
+ {
+ switch ($action) {
+ case 'create':
+ $extras = ["class" => "btn btn-outline btn-primary btn-circle", "target" => "_self", ...$extras];
+ $action = form_label(
+ '입력',
+ $action,
+ [
+ "data-src" => current_url() . '/' . $action . '?' . $viewDatas['uri']->getQuery(),
+ "data-bs-toggle" => "modal",
+ "data-bs-target" => "#index_action_form",
+ ...$extras
+ ]
+ );
+ break;
+ case 'modify':
+ $pk = $viewDatas['entity']->getPK();
+ $oldBatchJobUids = old("batchjob_uids", null);
+ $oldBatchJobUids = is_array($oldBatchJobUids) ? $oldBatchJobUids : [$oldBatchJobUids];
+ $checkbox = form_checkbox([
+ "id" => "checkbox_uid_{$pk}",
+ "name" => "batchjob_uids[]",
+ "value" => $pk,
+ "class" => "batchjobuids_checkboxs",
+ "checked" => in_array($pk, $oldBatchJobUids)
+ ]);
+ $action = $checkbox . form_label(
+ $viewDatas['cnt'],
+ $action,
+ [
+ "data-src" => current_url() . '/' . $action . '/' . $viewDatas['entity']->getPK(),
+ "data-bs-toggle" => "modal",
+ "data-bs-target" => "#index_action_form",
+ ...$extras
+ ]
+ );
+ break;
+ case 'delete':
+ $extras = ["class" => "btn btn-sm btn-danger btn-circle", "target" => "_self", ...$extras];
+ $action = anchor(
+ current_url() . '/' . $action . '/' . $viewDatas['entity']->getPK(),
+ ICONS['DELETE'],
+ $extras
+ );
+ break;
+ case 'batchjob':
+ $action = form_submit("batchjob_submit", '일괄처리', [
+ "formaction" => current_url() . '/batchjob',
+ "class" => "btn btn-outline btn-warning",
+ "onclick" => "return submitBatchJob()"
+ ]);
+ break;
+ case 'batchjob_delete':
+ $action = form_submit("batchjob_submit", '일괄삭제', [
+ "formaction" => current_url() . '/batchjob_delete',
+ "class" => "btn btn-outline btn-danger",
+ "onclick" => "return submitBatchJobDelete()"
+ ]);
+ break;
+ }
+ return $action;
+ }
+}
diff --git a/app/Helpers/MyLogHelper.php b/app/Helpers/MyLogHelper.php
new file mode 100644
index 0000000..1171f38
--- /dev/null
+++ b/app/Helpers/MyLogHelper.php
@@ -0,0 +1,74 @@
+$field ?: DEFAULTS['EMPTY'];
+ switch ($field) {
+ case Model::TITLE:
+ $value = form_label(
+ $value,
+ 'view',
+ [
+ "data-src" => current_url() . '/view/' . $viewDatas['entity']->getPK(),
+ "data-bs-toggle" => "modal",
+ "data-bs-target" => "#index_action_form",
+ "style" => "color: blue; cursor: pointer; font-weight:bold;",
+ ...$extras,
+ ]
+ );
+ break;
+ case 'user_uid':
+ $user_uids = [];
+ foreach (explode(DEFAULTS["DELIMITER_ROLE"], $value) as $key) {
+ $user_uids[] = $viewDatas['field_options'][$field][$key];
+ }
+ $value = implode(" , ", array: $user_uids);
+ break;
+ case 'content':
+ $value = nl2br($value);
+ break;
+ case 'status':
+ $value = $viewDatas['field_options'][$field][$value];
+ break;
+ default:
+ $value = parent::getFieldView($field, $viewDatas, $extras);
+ break;
+ }
+ return $value;
+ }
+ public function getListButton(string $action, array $viewDatas, array $extras = []): string
+ {
+ switch ($action) {
+ case 'create':
+ $action = "";
+ break;
+ case 'modify':
+ $action = $viewDatas['cnt'];
+ break;
+ case 'delete':
+ $action = "";
+ break;
+ case 'batchjob':
+ $action = "";
+ break;
+ case 'batchjob_delete':
+ $action = "";
+ break;
+ default:
+ $action = parent::getListButton($action, $viewDatas, $extras);
+ break;
+ }
+ return $action;
+ }
+}
diff --git a/app/Helpers/UserHelper.php b/app/Helpers/UserHelper.php
new file mode 100644
index 0000000..4ee9267
--- /dev/null
+++ b/app/Helpers/UserHelper.php
@@ -0,0 +1,93 @@
+ "form-control", "required" => "", ...$extras] : ["class" => "form-control", ...$extras];
+ }
+ $value = $value ?: DEFAULTS['EMPTY'];
+ switch ($field) {
+ case 'id':
+ case Model::TITLE:
+ $form = form_input($field, $value, $extras);
+ break;
+ case 'passwd':
+ case 'confirmpassword':
+ $form = form_password($field, "", ["autocomplete" => $field, ...$extras]);
+ break;
+ case 'email':
+ $form = form_input($field, $value, ["placeholder" => "예)test@example.com", ...$extras]);
+ break;
+ case 'mobile':
+ $form = form_input($field, $value, ["placeholder" => "예)010-0010-0010", ...$extras]);
+ break;
+ case 'role':
+ if (in_array($viewDatas['action'], ['create', 'modify'])) {
+ $forms = [];
+ foreach ($viewDatas['field_options'][$field] as $key => $label) {
+ $values = is_array($value) ? $value : explode(DEFAULTS["DELIMITER_ROLE"], $value);
+ $forms[] = form_checkbox(
+ "{$field}[]",
+ $key,
+ in_array($key, $values)
+ ) . $label;
+ }
+ $form = implode(" ", $forms);
+ } else {
+ $form = form_dropdown(
+ $field,
+ [
+ "" => lang($viewDatas['class_path'] . '.label.' . $field) . ' 선택',
+ ] + $viewDatas['field_options'][$field],
+ $value,
+ $extras
+ );
+ }
+ break;
+ default:
+ $form = parent::getFieldForm($field, $value, $viewDatas, $extras);
+ break;
+ }
+ return $form;
+ } //
+ public function getFieldView(string $field, array $viewDatas, array $extras = []): string
+ {
+ $value = $viewDatas['entity']->$field ?: DEFAULTS['EMPTY'];
+ switch ($field) {
+ case Model::TITLE:
+ $value = form_label(
+ $value,
+ 'view',
+ [
+ "data-src" => current_url() . '/view/' . $viewDatas['entity']->getPK(),
+ "data-bs-toggle" => "modal",
+ "data-bs-target" => "#index_action_form",
+ "style" => "color: blue; cursor: pointer; font-weight:bold;",
+ ...$extras,
+ ]
+ );
+ break;
+ case 'role':
+ $roles = [];
+ foreach (explode(DEFAULTS["DELIMITER_ROLE"], $value) as $key) {
+ $roles[] = $viewDatas['field_options'][$field][$key];
+ }
+ $value = implode(" , ", $roles);
+ break;
+ default:
+ $value = parent::getFieldView($field, $viewDatas, $extras);
+ break;
+ }
+ return $value;
+ } //
+}
diff --git a/app/Language/.gitkeep b/app/Language/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/app/Language/en/MyLog.php b/app/Language/en/MyLog.php
new file mode 100644
index 0000000..36e24eb
--- /dev/null
+++ b/app/Language/en/MyLog.php
@@ -0,0 +1,19 @@
+ "Log 정보",
+ 'label' => [
+ 'uid' => "번호",
+ 'class_name' => "클래스",
+ 'method_name' => "함수",
+ 'title' => "제목",
+ 'user_uid' => "사용자",
+ 'content' => "내용",
+ 'status' => "상태",
+ 'updated_at' => "수정일",
+ 'created_at' => "작성일",
+ ],
+ "STATUS" => [
+ "use" => "완료",
+ "unuse" => "실패",
+ ],
+];
diff --git a/app/Language/en/User.php b/app/Language/en/User.php
new file mode 100644
index 0000000..a9e8f3b
--- /dev/null
+++ b/app/Language/en/User.php
@@ -0,0 +1,30 @@
+ "계정정보",
+ 'label' => [
+ 'uid' => "번호",
+ 'id' => "계정",
+ 'passwd' => "암호",
+ 'confirmpassword' => "암호확인",
+ 'email' => "메일",
+ 'mobile' => "연락처",
+ 'role' => "권한",
+ 'name' => "이름",
+ 'status' => "상태",
+ 'updated_at' => "수정일",
+ 'created_at' => "작성일",
+ ],
+ "ROLE" => [
+ "user" => "일반회원",
+ "vip" => "VIP회원",
+ "manager" => "관리자",
+ "cloudflare" => "Cloudflare관리자",
+ "firewall" => "firewall관리자",
+ "director" => "감독자",
+ "master" => "마스터",
+ ],
+ "STATUS" => [
+ "use" => "사용",
+ "unuse" => "사용않함",
+ ],
+];
diff --git a/app/Language/en/Validation.php b/app/Language/en/Validation.php
new file mode 100644
index 0000000..54d1e7a
--- /dev/null
+++ b/app/Language/en/Validation.php
@@ -0,0 +1,4 @@
+_libraryDatas)) {
+ return null;
+ }
+ return $this->_libraryDatas[$name];
+ }
+
+ final public function __set($name, $value): void
+ {
+ $this->_libraryDatas[$name] = $value;
+ }
+}
diff --git a/app/Libraries/MySocket/GoogleSocket/API.php b/app/Libraries/MySocket/GoogleSocket/API.php
new file mode 100644
index 0000000..16b0a1a
--- /dev/null
+++ b/app/Libraries/MySocket/GoogleSocket/API.php
@@ -0,0 +1,128 @@
+_client === null) {
+ $this->_client = new Client();
+ $this->_client->setClientId(env('socket.google.client.id'));
+ $this->_client->setClientSecret(env('socket.google.client.key'));
+ $this->_client->setRedirectUri(base_url(env('socket.google.client.callback_url')));
+ $this->_client->addScope(Oauth2::USERINFO_EMAIL);
+ $this->_client->addScope(Oauth2::USERINFO_PROFILE);
+ // $this->setPrompt('select_account consent');
+ // $this->setAccessType('offline');
+ // SSL 검증 비활성화
+ $this->_client->setHttpClient(new \GuzzleHttp\Client(['verify' => false]));
+ // 사용자 정의 CA 번들 사용
+ // $this->setHttpClient(new \GuzzleHttp\Client(['verify' => '/path/to/cacert.pem']));
+ }
+ return $this->_client;
+ }
+
+ public function createAuthUrl(): string
+ {
+ return $this->getClient()->createAuthUrl();
+ }
+
+ //TokenInfo
+ // (object) array(
+ // 'access_token' => 'sdfsdfsdfsdf',
+ // 'expires_in' => 3599,
+ // 'refresh_token' => 'sdfsdf',
+ // 'scope' => 'https://www.googleapis.com/auth/userinfo.profile openid https://www.googleapis.com/auth/userinfo.email',
+ // 'token_type' => 'Bearer',
+ // 'id_token' => 'fadfasdfsadf.sdfsdf.sdfsd',
+ // )
+ // id_token(.을기준으로 base64_decode):
+ // DEBUG - 2024-10-10 07:25:01 --> array (
+ // 'alg' => 'RS256',
+ // 'kid' => 'a50f6e70ef4bsdfsdffb8f54dce9ee',
+ // 'typ' => 'JWT',
+ // )
+ // DEBUG - 2024-10-10 07:25:01 --> array (
+ // 'iss' => 'accounts.google.com',
+ // 'azp' => '105607sdfsdfsdfsdfogleusercontent.com',
+ // 'aud' => '1056073563687sdfsdfsdftent.com',
+ // 'sub' => '103667492342341096838',
+ // 'email' => 'sdfsdfsdf@gmail.com',
+ // 'email_verified' => true,
+ // 'at_hash' => 'RKDNDFSrkeZ_LWg',
+ // 'iat' => 1728df545102,
+ // 'exp' => 172854df8702,
+ // )
+ // DEBUG - 2024-10-10 07:25:01 --> NULL
+ public function setToken(string $access_code): void
+ {
+ // 토큰 정보 가져오기
+ $tokenInfo = $this->getClient()->fetchAccessTokenWithAuthCode($access_code);
+ if (isset($tokenInfo['error'])) {
+ throw new ConfigException($tokenInfo['error']);
+ }
+ // log_message("debug", var_export($tokenInfo, true));
+ $this->_access_token = $tokenInfo[$this->_token_name];
+ // Google Service에 접근하기 위해 Access Token 설정
+ $this->getClient()->setAccessToken([
+ 'access_token' => $this->_access_token,
+ 'expires_in' => 3600,
+ 'created' => time(),
+ ]);
+ if ($this->getClient()->isAccessTokenExpired()) {
+ $this->getClient()->refreshToken($tokenInfo['refresh_token']);
+ }
+ // 세션에 Token 값 설정
+ $this->getSession()->set($this->_token_name, $this->_access_token);
+ }
+
+ // DEBUG - 2024-10-10 12:00:13 --> \Google\Service\Oauth2\Userinfo::__set_state(array(
+ // 'internal_gapi_mappings' =>
+ // array (
+ // 'familyName' => 'family_name',
+ // 'givenName' => 'given_name',
+ // 'verifiedEmail' => 'verified_email',
+ // ),
+ // 'modelData' =>
+ // array (
+ // 'verified_email' => true,
+ // 'given_name' => '길동',
+ // 'family_name' => '홍',
+ // ),
+ // 'processed' =>
+ // array (
+ // ),
+ // 'email' => 'sdfsdd@gmail.com',
+ // 'familyName' => '홍',
+ // 'gender' => NULL,
+ // 'givenName' => '길동',
+ // 'hd' => NULL,
+ // 'id' => '103667499972324688341096838',
+ // 'link' => NULL,
+ // 'locale' => NULL,
+ // 'name' => '홍길동',
+ // 'picture' => 'https://lh3.googleusercontent.com/a/VDSJj3D925VP-pt9ppnwsPtm4pyYE6IO7bei-RyVM0Q=s96-c',
+ // 'verifiedEmail' => true,
+ // ))
+ public function getUserSNSEntity(): UserSNSEntity
+ {
+ $this->getClient()->setAccessToken($this->getToken());
+ $oauth = new Oauth2($this->getClient());
+ $userInfo = $oauth->userinfo->get();
+ $detail = var_export($userInfo, true);
+ // log_message("debug", $detail);
+ // 사용자정보 설정하기
+ return $this->setUserSNSEntity($userInfo->id, $userInfo->name, $userInfo->email, $detail);
+ }
+}
diff --git a/app/Libraries/MySocket/GoogleSocket/CURL.php b/app/Libraries/MySocket/GoogleSocket/CURL.php
new file mode 100644
index 0000000..7f54846
--- /dev/null
+++ b/app/Libraries/MySocket/GoogleSocket/CURL.php
@@ -0,0 +1,171 @@
+_client === null) {
+ $this->_client = new Client();
+ }
+ return $this->_client;
+ }
+
+ public function createAuthUrl(): string
+ {
+ $options = http_build_query([
+ 'response_type' => 'code',
+ 'client_id' => env('socket.google.client.id'),
+ 'redirect_uri' => base_url(env('socket.google.client.callback_url')),
+ 'scope' => "https://www.googleapis.com/auth/userinfo.profile openid https://www.googleapis.com/auth/userinfo.email",
+ 'access_type' => 'offline',
+ 'prompt' => 'consent'
+ ]);
+ //기본적으로 검색할 범위를 지정하고 사용자를 Google OAuth 동의 화면으로 리디렉션합니다
+ return "https://accounts.google.com/o/oauth2/v2/auth?" . $options;
+ }
+
+ //TokenInfo
+ // (object) array(
+ // 'access_token' => 'sdfsdfsdfsdf',
+ // 'expires_in' => 3599,
+ // 'refresh_token' => 'sdfsdf',
+ // 'scope' => 'https://www.googleapis.com/auth/userinfo.profile openid https://www.googleapis.com/auth/userinfo.email',
+ // 'token_type' => 'Bearer',
+ // 'id_token' => 'fadfasdfsadf.sdfsdf.sdfsd',
+ // )
+ // id_token(.을기준으로 base64_decode):
+ // DEBUG - 2024-10-10 07:25:01 --> array (
+ // 'alg' => 'RS256',
+ // 'kid' => 'a50f6e70ef4bsdfsdffb8f54dce9ee',
+ // 'typ' => 'JWT',
+ // )
+ // DEBUG - 2024-10-10 07:25:01 --> array (
+ // 'iss' => 'accounts.google.com',
+ // 'azp' => '105607sdfsdfsdfsdfogleusercontent.com',
+ // 'aud' => '1056073563687sdfsdfsdftent.com',
+ // 'sub' => '103667492342341096838',
+ // 'email' => 'sdfsdfsdf@gmail.com',
+ // 'email_verified' => true,
+ // 'at_hash' => 'RKDNDFSrkeZ_LWg',
+ // 'iat' => 1728df545102,
+ // 'exp' => 172854df8702,
+ // )
+ // DEBUG - 2024-10-10 07:25:01 --> NULL
+
+ public function setToken(string $access_code): void
+ {
+ $options = [
+ 'code' => $access_code,
+ 'client_id' => env('socket.google.client.id'),
+ 'client_secret' => env('socket.google.client.key'),
+ 'redirect_uri' => base_url(env('socket.google.client.callback_url')),
+ 'grant_type' => 'authorization_code',
+ ];
+ $response = $this->post("https://accounts.google.com/o/oauth2/token", $options);
+ if ($response->getStatusCode() != 200) {
+ $message = sprintf(
+ "Google: %s에서 API 호출 실패: \n--request options--\n%s\n--response--\n%s\n",
+ __FUNCTION__,
+ var_export($options, true),
+ var_export($response, true)
+ );
+ log_message("error", $message);
+ throw new \Exception($message);
+ }
+ $tokenInfo = json_decode($response->getBody(), true);
+ // log_message("debug", var_export($tokenInfo, true));
+ if (isset($tokenInfo['error']) || !isset($tokenInfo[$this->_token_name]) || empty($tokenInfo[$this->_token_name])) {
+ $message = sprintf(
+ "Google: Token 정보가 없습니다.\n--tokenInfo--\n%s\n",
+ __FUNCTION__,
+ var_export($tokenInfo, true)
+ );
+ log_message("error", $message);
+ throw new \Exception($message);
+ }
+ //JWT값
+ // $jwts = explode('.', $tokenInfo['id_token']);
+ // foreach ($jwts as $jwt) {
+ // $info = json_decode(base64_decode($jwt), true);
+ // // log_message("debug", var_export($info, true));
+ // }
+ // 토큰 정보 가져오기
+ $this->_access_token = $tokenInfo[$this->_token_name];
+ // 세션에 Token 값 설정
+ $this->getSession()->set($this->_token_name, $this->_access_token);
+ }
+
+ // throw new \Exception(__METHOD__ . "에서 데이터 처리 필요");
+ // DEBUG - 2023-07-13 12:54:51 --> \Google\Service\Oauth2\Userinfo::__set_state(array(
+ // 'internal_gapi_mappings' =>
+ // 'familyName' => 'family_name',
+ // 'givenName' => 'given_name',
+ // 'verifiedEmail' => 'verified_email',
+ // ),
+ // 'modelData' =>
+ // array (
+ // 'verified_email' => true,
+ // 'given_name' => '이름',
+ // 'family_name' => '성',
+ // ),
+ // 'processed' =>
+ // array (
+ // ),
+ // 'email' => 'twsdfsew342s@gmail.com',
+ // 'familyName' => '성',
+ // 'gender' => NULL,
+ // 'givenName' => '이름',
+ // 'hd' => NULL,
+ // 'id' => '103667492432234234236838324',
+ // 'link' => NULL,
+ // 'locale' => 'ko',
+ // 'name' => '성이름',
+ // 'picture' => 'https://lh3.googleusercontent.com/a/AAcHTteFSgefsdfsdRJBkJA2tBEmg4PQrvI1Ta_5IXu5=s96-c',
+ // 'verifiedEmail' => true,
+ // ))
+ public function getUserSNSEntity(): UserSNSEntity
+ {
+ $options = [
+ "headers" => [
+ "Authorization" => "Bearer {$this->getToken()}",
+ "Accept" => "application/json",
+ 'User-Agent' => $this->getUserAgent()
+ ],
+ ];
+ $response = $this->get("https://www.googleapis.com/oauth2/v3/userinfo", $options);
+ if ($response->getStatusCode() != 200) {
+ $message = sprintf(
+ "Google: %s에서 API 호출 실패: \n--request options--\n%s\n--response--\n%s\n",
+ __FUNCTION__,
+ var_export($options, true),
+ var_export($response, true)
+ );
+ log_message("error", $message);
+ throw new \Exception($message);
+ }
+ $userInfo = json_decode($response->getBody(), true);
+ $detail = var_export($userInfo, true);
+ // log_message("debug", $detail);
+ if (isset($userInfo['error']) || !isset($userInfo['email']) || empty($userInfo['email'])) {
+ $message = sprintf(
+ "Google: User 정보가 없습니다.\n--userInfo--\n%s\n",
+ __FUNCTION__,
+ var_export($userInfo, true)
+ );
+ log_message("error", $message);
+ throw new \Exception($message);
+ }
+ // 사용자정보 설정하기
+ return $this->setUserSNSEntity($userInfo["id"], $userInfo["name"], $userInfo["email"], $detail);
+ }
+}
diff --git a/app/Libraries/MySocket/GoogleSocket/GoogleSocket.php b/app/Libraries/MySocket/GoogleSocket/GoogleSocket.php
new file mode 100644
index 0000000..f4cc27d
--- /dev/null
+++ b/app/Libraries/MySocket/GoogleSocket/GoogleSocket.php
@@ -0,0 +1,77 @@
+_session == null) {
+ $this->_session = \Config\Services::session();
+ }
+ return $this->_session;
+ }
+ final public function getToken(): string
+ {
+ return $this->getSession()->get($this->_token_name);
+ }
+ final public function getSite(): string
+ {
+ return $this->_site;
+ }
+ final protected function getModel(): UserSNSModel
+ {
+ if ($this->_model === null) {
+ $this->_model = model(UserSNSModel::class);
+ }
+ return $this->_model;
+ }
+ final protected function setUserSNSEntity(string $id, string $name, string $email, string $detail): UserSNSEntity
+ {
+ $this->getModel()->where(UserSNSModel::SITE, $this->getSite());
+ $entity = $this->getModel()->getEntityByID($id);
+ if ($entity === null) {
+ //Transaction Start
+ $this->getModel()->transStart();
+ try {
+ //없다면 새로 등록
+ $formDatas = [
+ 'site' => $this->getSite(),
+ 'id' => $id,
+ 'name' => $name,
+ 'email' => $email,
+ 'detail' => $detail,
+ 'status' => 'unuse',
+ ];
+ $entity = $this->getModel()->create($formDatas);
+ $this->getModel()->transCommit();
+ } catch (\Exception $e) {
+ //Transaction Rollback
+ $this->getModel()->transRollback();
+ log_message("error", $e->getMessage());
+ throw new \Exception(__FUNCTION__ . " 실패하였습니다.\n" . $e->getMessage());
+ }
+ }
+ //상태가 use(승인완료)가 아니라면
+ if ($entity->status !== DEFAULTS['STATUS']) {
+ throw new PageNotFoundException("{$entity->getSite()}의{$entity->getEmail()}:{$entity->getTitle()}님은 {$entity->status}입니다 ");
+ }
+ return $entity;
+ }
+}
diff --git a/app/Libraries/MySocket/MySocket.php b/app/Libraries/MySocket/MySocket.php
new file mode 100644
index 0000000..da66c0f
--- /dev/null
+++ b/app/Libraries/MySocket/MySocket.php
@@ -0,0 +1,91 @@
+_cookieJar === null) {
+ $this->_cookieJar = new CookieJar();
+ }
+ return $this->_cookieJar;
+ }
+ protected function getUserAgent(): string
+ {
+ // User-Agent 목록 배열
+ $userAgents = [
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15',
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1',
+ 'Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Mobile Safari/537.36'
+ ];
+ return $userAgents[array_rand($userAgents)];
+ }
+ protected function getRequestOptions(string $method, array $options = [], array $headers = []): array
+ {
+ //cookies->쿠키값 , timeout->5초 안에 응답이 없으면 타임아웃
+ //method가 get이면 $request['query'] = $options , 다른것이면 $request['json] = $options
+ $options = [
+ // 'cookies' => $this->getCookieJar(),
+ 'timeout' => env("socket.web.timeout") ?? 5,
+ 'headers' => $headers,
+ in_array($method, ['get']) ? 'query' : 'json' => $options
+ ];
+ return $options;
+ }
+ public function request(string $method, $uri = '', array $options = [], array $headers = []): ResponseInterface
+ {
+ if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) {
+ throw new \InvalidArgumentException("{$method} => Request method must be get, post, put, patch, or delete");
+ }
+ try {
+ $options = $this->getRequestOptions($method, $options, $headers);
+ $response = $this->getClient()->$method($uri, $options);
+ $body = json_decode(json: $response->getBody());
+ if (!$body->success) {
+ $message = sprintf(
+ "%s에서 {$uri} 실패:\nrequest:%s\nresponse:%s",
+ $method,
+ $uri,
+ var_export($options, true),
+ var_export($response, true)
+ );
+ log_message("error", $message);
+ throw new ResponseException($message);
+ }
+ return $response;
+ } catch (RequestException $err) {
+ throw ResponseException::fromRequestException($err);
+ }
+ }
+ final public function get($uri, array $options = [], array $headers = []): ResponseInterface
+ {
+ return $this->request(__FUNCTION__, $uri, $options, $headers);
+ }
+ final public function post($uri, array $options = [], array $headers = []): ResponseInterface
+ {
+ return $this->request(__FUNCTION__, $uri, $options, $headers);
+ }
+ final public function put($uri, array $options = [], array $headers = []): ResponseInterface
+ {
+ return $this->request(__FUNCTION__, $uri, $options, $headers);
+ }
+ final public function patch($uri, array $options = [], array $headers = []): ResponseInterface
+ {
+ return $this->request(__FUNCTION__, $uri, $options, $headers);
+ }
+ final public function delete($uri, array $options = [], array $headers = []): ResponseInterface
+ {
+ return $this->request(__FUNCTION__, $uri, $options, $headers);
+ }
+}
diff --git a/app/Libraries/MySocket/WebSocket.php b/app/Libraries/MySocket/WebSocket.php
new file mode 100644
index 0000000..fbe94ad
--- /dev/null
+++ b/app/Libraries/MySocket/WebSocket.php
@@ -0,0 +1,48 @@
+_host = $host;
+ }
+ public function getClient(): Client
+ {
+ if ($this->_client === null) {
+ $this->_client = new Client();
+ }
+ return $this->_client;
+ }
+ final public function getURL($uri): string
+ {
+ // url에 http 나 https가 포함되어 있지않으면
+ if (!preg_match('~^(http|https)://~i', $uri)) {
+ $uri = "{$this->_host}{$uri}";
+ }
+ return $uri;
+ }
+ public function getResponse($uri, array $options = []): ResponseInterface
+ {
+ $response = $this->get($this->getURL($uri), $options);
+ if ($response->getStatusCode() != 200) {
+ throw new \Exception("error", __FUNCTION__ .
+ "=> {$uri} 접속실패: " .
+ $response->getStatusCode());
+ }
+ return $response;
+ }
+ public function getContent(string $uri, array $options = []): string
+ {
+ $response = $this->getResponse($uri, $options);
+ // return $response->getBody()->getContents();
+ return $response->getBody();
+ }
+}
diff --git a/app/Libraries/MyStorage/FileStorage.php b/app/Libraries/MyStorage/FileStorage.php
new file mode 100644
index 0000000..167f9b4
--- /dev/null
+++ b/app/Libraries/MyStorage/FileStorage.php
@@ -0,0 +1,111 @@
+_path = $path;
+ }
+ final public function getPath(): string
+ {
+ return $this->_path;
+ }
+ public function getUploadPath(): string
+ {
+ return "uploads";
+ }
+ final public function getFullPath(): string
+ {
+ $full_path = WRITEPATH . $this->getUploadPath() . DIRECTORY_SEPARATOR . $this->getPath();
+ $this->mkdir_FileTrait($full_path);
+ return $full_path;
+ }
+ public function getUploadURL(): string
+ {
+ return "uploads";
+ }
+ final public function getOriginName(): string
+ {
+ return $this->_originName;
+ }
+ final public function setOriginName(string $originName): void
+ {
+ $this->_originName = $originName;
+ }
+ final public function getOriginContent(): string
+ {
+ return $this->_originContent;
+ }
+ final public function setOriginContent(string $originContent): void
+ {
+ $this->_originContent = $originContent;
+ }
+ final public function getOriginMediaTag(): string
+ {
+ return $this->_originMediaTag;
+ }
+ final public function setOriginMediaTag(string $originMediaTag): void
+ {
+ $this->_originMediaTag = $originMediaTag;
+ }
+ final public function getOriginSequence(): int
+ {
+ return $this->_originSequence;
+ }
+ final public function setOriginSequence(int $originSequence): void
+ {
+ $this->_originSequence = $originSequence;
+ }
+ final public function getMimeType(): string
+ {
+ return $this->_mimeType;
+ }
+ final public function getFileSize(): int
+ {
+ return $this->_fileSize;
+ }
+ public function save(): static
+ {
+ // log_message("notice", __FUNCTION__ . " 원본파일 {$this->getOriginName()} 작업 시작 2");
+ $save_file = $this->getFullPath() . DIRECTORY_SEPARATOR . $this->getOriginName();
+ log_message("debug", __FUNCTION__ . " {$save_file} 작업 시작");
+ //중복된 파일명인지 확인후 새로운 이름으로 저장
+ if (file_exists($save_file)) {
+ switch (env("mangboard.uploads.file.collision")) {
+ case "unique":
+ $file_name = $this->getUniqueName_FileTrait($this->getFullPath(), $this->getOriginName());
+ log_message("notice", __FUNCTION__ . " 파일명 변경 : 원본파일 {$this->getOriginName()}->저장파일 {$file_name}");
+ $this->setOriginName($file_name);
+ $save_file = $this->getFullPath() . DIRECTORY_SEPARATOR . $this->getOriginName();
+ break;
+ case "notallow":
+ default:
+ throw new \Exception(__FUNCTION__ . " {$this->getOriginName()} 는 이미 존재하는 파일입니다.");
+ // break;
+ }
+ }
+ //원본이미지 저장
+ if (!file_put_contents($save_file, $this->getOriginContent())) {
+ throw new \Exception(__FUNCTION__ . " 파일저장 실패:{$save_file}");
+ }
+ $this->_mimeType = mime_content_type($save_file);
+ $this->_fileSize = filesize($save_file);
+ log_message("notice", __FUNCTION__ . " 원본파일 {$this->getOriginName()} 작업 완료");
+ return $this;
+ }
+}
diff --git a/app/Models/.gitkeep b/app/Models/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/app/Models/CommonModel.php b/app/Models/CommonModel.php
new file mode 100644
index 0000000..26962b3
--- /dev/null
+++ b/app/Models/CommonModel.php
@@ -0,0 +1,229 @@
+ field가 array 입니다.\n" . var_export($field, true));
+ }
+ switch ($field) {
+ case $this->getPKField():
+ // 수동입력인 경우
+ if (!$this->useAutoIncrement) {
+ $rule = "required|regex_match[/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/]";
+ $rule .= $action == "create" ? "|is_unique[{$this->table}.{$field}]" : "";
+ } else {
+ $rule = "required|numeric";
+ }
+ break;
+ case $this->getTitleField():
+ $rule = "required|string";
+ break;
+ case 'image':
+ $rule = "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 "status":
+ $rule = "if_exist|in_list[use,unuse]";
+ break;
+ case "updated_at":
+ case "created_at":
+ case "deleted_at":
+ $rule = "if_exist|valid_date";
+ break;
+ default:
+ $rule = "if_exist|string";
+ break;
+ }
+ return $rule;
+ }
+
+ final public function getFieldRules(string $action, array $fields, $rules = []): array
+ {
+ foreach ($fields as $field) {
+ $rules[$field] = $this->getFieldRule($action, $field);
+ }
+ return $rules;
+ }
+
+ public function getFormFieldOption(string $field, array $options = []): array
+ {
+ switch ($field) {
+ default:
+ foreach ($this->getEntitys() as $entity) {
+ $options[$entity->getPK()] = $entity->getTitle();
+ }
+ break;
+ }
+ // dd($options);
+ return $options;
+ }
+ // create, modify 직전 작업용 작업
+ protected function convertEntityData(string $field, array $formDatas): mixed
+ {
+ switch ($field) {
+ case $this->getPKField():
+ // $formDatas에 전달된 값이 없는 경우
+ if (!array_key_exists($field, $formDatas)) {
+ $randomBytes = bin2hex(random_bytes(32));
+ $value = sprintf(
+ '%08s-%04s-%04x-%04x-%12s',
+ substr($randomBytes, 0, 8),
+ substr($randomBytes, 8, 4),
+ substr($randomBytes, 12, 4),
+ substr($randomBytes, 16, 4),
+ substr($randomBytes, 20, 12)
+ );
+ } else {
+ $value = $formDatas[$field];
+ }
+ break;
+ case "editor": // content 등 textarea를 사용한 Field
+ $value = htmlentities($formDatas[$field], ENT_QUOTES);
+ break;
+ default:
+ $value = $formDatas[$field];
+ break;
+ }
+ return $value;
+ }
+
+ private function save_process($entity): mixed
+ {
+ try {
+ // 최종 변경사항이 없으면
+ if (!$entity->hasChanged()) {
+ throw new \Exception(__FUNCTION__ . " 변경된 내용이 없습니다.");
+ }
+ // log_message("debug", var_export($entity, true));
+ // 최종 저장 시 오류 발생하면
+ if (!$this->save($entity)) {
+ throw new \Exception("저장오류:" . var_export($this->errors(), true));
+ }
+ log_message("debug", $this->getTable() . " => " . __FUNCTION__ . " DB에 {$entity->getTitle()} 저장이 완료되었습니다.");
+ // log_message("debug", $this->getLastQuery());
+ return $entity;
+ } catch (\Exception $e) {
+ $message = sprintf(
+ "\n------%s SQL오류-----\n%s\n%s\n------------------------------\n",
+ __FUNCTION__,
+ $this->getLastQuery(),
+ $e->getMessage()
+ );
+ throw new \Exception($message);
+ }
+ }
+ final public function create(array $formDatas, $entity): mixed
+ {
+ // Field에 맞는 Validation Rule 재정의
+ $this->setValidationRules($this->getFieldRules('create', $this->allowedFields));
+ // 저장하기 전에 데이터 값 변경이 필요한 Field
+ foreach (array_keys($formDatas) as $field) {
+ $entity->$field = $this->convertEntityData($field, $formDatas);
+ }
+ // dd($entity);
+ $entity = $this->save_process($entity);
+ // primaryKey가 자동입력이면
+ if ($this->useAutoIncrement) {
+ $pkField = $this->getPKField();
+ $entity->$pkField = $this->getInsertID();
+ }
+ // log_message("debug", $this->getTable() . " => " . __FUNCTION__ . " DB 작업 완료");
+ return $entity;
+ }
+
+ final protected function modify(array $formDatas, $entity): mixed
+ {
+ // Field에 맞는 Validation Rule 재정의
+ $this->setValidationRules($this->getFieldRules('modify', $this->allowedFields));
+ // 저장하기 전에 데이터 값 변경이 필요한 Field
+ foreach (array_keys($formDatas) as $field) {
+ $entity->$field = $this->convertEntityData($field, $formDatas);
+ }
+ $this->save_process($entity);
+ // log_message("debug", $this->getTable() . " => " . __FUNCTION__ . " DB 작업 완료");
+ return $entity;
+ }
+
+ final public function setList_FieldFilter(string $field, int|string $value): void
+ {
+ $this->where($this->getTable() . '.' . $field, $value);
+ }
+ final public function setList_DateFilter(string $start, string $end, $field = "created_at"): void
+ {
+ if ($start !== DEFAULTS['EMPTY']) {
+ $this->where($this->getTable() . ".{$field} >= '{$start} 00:00:00'");
+ }
+ if ($end !== DEFAULTS['EMPTY']) {
+ $this->where($this->getTable() . ".{$field} <= '{$end} 23:59:59'");
+ }
+ }
+}
diff --git a/app/Models/MyLogModel.php b/app/Models/MyLogModel.php
new file mode 100644
index 0000000..774ffe1
--- /dev/null
+++ b/app/Models/MyLogModel.php
@@ -0,0 +1,61 @@
+ field가 array 입니다.\n" . var_export($field, true));
+ }
+ switch ($field) {
+ case "user_uid":
+ $rule = "if_exist|numeric";
+ break;
+ default:
+ $rule = parent::getFieldRule($action, $field);
+ break;
+ }
+ return $rule;
+ }
+ //List 검색용
+ public function setList_WordFilter(string $word): void
+ {
+ $this->orLike(self::TABLE . "." . self::TITLE, $word, 'both');
+ $this->orLike(self::TABLE . '.content', $word, 'both');
+ }
+}
diff --git a/app/Models/UserModel.php b/app/Models/UserModel.php
new file mode 100644
index 0000000..ad72b0f
--- /dev/null
+++ b/app/Models/UserModel.php
@@ -0,0 +1,101 @@
+ field가 array 입니다.\n" . var_export($field, true));
+ }
+ switch ($field) {
+ case "id":
+ $rule = "required|trim|min_length[4]|max_length[20]";
+ $rule .= $action == "create" ? "|is_unique[{$this->table}.{$field}]" : "";
+ break;
+ case "passwd":
+ $rule = $action == "create" ? "required" : "if_exist" . "|trim|string";
+ break;
+ case "confirmpassword":
+ $rule = $action == "create" ? "required" : "if_exist" . "|trim|string|matches[passwd]";
+ break;
+ case "email":
+ $rule = "required|trim|valid_email";
+ break;
+ case "role":
+ $rule = "required|trim|string";
+ break;
+ default:
+ $rule = parent::getFieldRule($action, $field);
+ break;
+ }
+ return $rule;
+ }
+ protected function convertEntityData(string $field, array $formDatas): mixed
+ {
+ switch ($field) {
+ case "passwd":
+ $value = password_hash($formDatas[$field], PASSWORD_DEFAULT);
+ break;
+ case "confirmpassword":
+ $value = password_hash($formDatas[$field], PASSWORD_DEFAULT);
+ break;
+ default:
+ $value = parent::convertEntityData($field, $formDatas);
+ break;
+ }
+ return $value;
+ }
+ public function getFormFieldOption(string $field, array $options = []): array
+ {
+ switch ($field) {
+ default:
+ $this->orderBy(self::TITLE, 'asc');
+ $options = parent::getFormFieldOption($field, $options);
+ break;
+ }
+ return $options;
+ }
+ //List 검색용
+ public function setList_WordFilter(string $word): void
+ {
+ $this->orLike(self::TABLE . '.id', $word, 'both');
+ $this->orLike(self::TABLE . "." . self::TITLE, $word, 'both');
+ $this->orLike(self::TABLE . '.email', $word, 'both');
+ }
+}
diff --git a/app/Services/Auth/AuthService.php b/app/Services/Auth/AuthService.php
new file mode 100644
index 0000000..f5970d7
--- /dev/null
+++ b/app/Services/Auth/AuthService.php
@@ -0,0 +1,136 @@
+_session === null) {
+ $this->_session = \Config\Services::session();
+ }
+ return $this->_session;
+ }
+
+ final public function getModel(): UserModel
+ {
+ if ($this->_model === null) {
+ $this->_model = new UserModel();
+ }
+ return $this->_model;
+ }
+
+ private function getAuthInfo(string $key = ""): array|string
+ {
+ $authInfo = $this->getSession()->get(SESSION_NAMES['AUTH']);
+ if ($key) {
+ return $authInfo[$key] ?? "";
+ }
+ return $authInfo;
+ }
+
+ final public function getUIDByAuthInfo(): string
+ {
+ return $this->getAuthInfo('uid');
+ }
+
+ final public function getIDByAuthInfo(): string
+ {
+ return $this->getAuthInfo('id');
+ }
+
+
+ final public function getNameByAuthInfo(): string
+ {
+ return $this->getAuthInfo('name');
+ }
+
+ final public function getRoleByAuthInfo(): string
+ {
+ return $this->getAuthInfo('role');
+ }
+
+
+ final public function isLoggedIn(): bool
+ {
+ return $this->getSession()->has(SESSION_NAMES['ISLOGIN']);
+ }
+
+ final public function isAccessRole(array $roles): bool
+ {
+ $role = $this->getRoleByAuthInfo();
+ if ($role === "") {
+ return false;
+ }
+ $myRoles = explode(DEFAULTS['DELIMITER_ROLE'], $role);
+ // 교집합이 없으면 false
+ return !empty(array_intersect($myRoles, $roles));
+ }
+
+ final public function pushCurrentUrl(string $url): void
+ {
+ $this->getSession()->set('url_stack', $url);
+ }
+
+ final public function popPreviousUrl(): string
+ {
+ $url = $this->getSession()->get('url_stack') ?? "";
+ if (!empty($url)) {
+ $this->pushCurrentUrl("");
+ return $url;
+ }
+ return '/'; // 기본 URL
+ }
+
+ final public function login(UserEntity $entity): void
+ {
+ $this->getSession()->set(SESSION_NAMES['ISLOGIN'], true);
+ $this->getSession()->set(SESSION_NAMES['AUTH'], [
+ 'uid' => $entity->getPK(),
+ 'id' => $entity->getID(),
+ 'name' => $entity->getTitle(),
+ 'role' => $entity->role
+ ]);
+ }
+
+ final public function logout(): void
+ {
+ // 세션 데이터 삭제
+ $this->getSession()->remove(SESSION_NAMES['ISLOGIN']);
+ $this->getSession()->remove(SESSION_NAMES['AUTH']);
+
+ // 모든 세션 데이터 삭제
+ $this->getSession()->destroy();
+
+ // 세션 쿠키 삭제
+ if (ini_get("session.use_cookies")) {
+ $params = session_get_cookie_params();
+ setcookie(
+ session_name(),
+ '',
+ time() - 42000,
+ $params["path"],
+ $params["domain"],
+ $params["secure"],
+ $params["httponly"]
+ );
+ }
+
+ // 세션 재생성
+ session_start();
+ $this->getSession()->regenerate(true);
+ }
+}
diff --git a/app/Services/Auth/GoogleService.php b/app/Services/Auth/GoogleService.php
new file mode 100644
index 0000000..675dfe3
--- /dev/null
+++ b/app/Services/Auth/GoogleService.php
@@ -0,0 +1,55 @@
+_mySocket = $mySocket;
+ parent::__construct();
+ }
+ final public function getClassName(): string
+ {
+ return "Google";
+ }
+ final public function getClassPath(): string
+ {
+ return $this->getClassName();
+ }
+ public function getMySocket(string $access_code): mixed
+ {
+ if ($this->_mySocket === null) {
+ throw new \Exception("Socket 방식이 지정되지 않았습니다.");
+ }
+ $this->_mySocket->setToken($this->_access_code);
+ return $this->_mySocket;
+ }
+
+ public function checkUser(string $access_code): UserEntity
+ {
+ try {
+ // Google 서비스 설정
+ $userSNS_entity = $this->getMySocket($access_code)->getUserSNSEntity();
+ // local db 사용와의 연결 확인
+ $user_entity = $this->getModel()->getEntityByPK($userSNS_entity->getParent());
+ if ($user_entity === null) {
+ throw new PageNotFoundException("회원[{$userSNS_entity->getTitle()}]님은 아직 로컬사용자 연결이 이루어지지 않았습니다.");
+ }
+ return $user_entity;
+ } catch (\Google_Service_Exception $e) {
+ log_message('error', '구글 서비스 예외: ' . $e->getMessage());
+ throw new PageNotFoundException("구글 로그인 중 오류가 발생했습니다. 다시 시도해 주세요.");
+ } catch (\Exception $e) {
+ log_message('error', $e->getMessage());
+ throw new PageNotFoundException("관리자에게 문의하시기 바랍니다.\n{$e->getMessage()}");
+ }
+ }
+}
diff --git a/app/Services/Auth/LocalService.php b/app/Services/Auth/LocalService.php
new file mode 100644
index 0000000..c240a37
--- /dev/null
+++ b/app/Services/Auth/LocalService.php
@@ -0,0 +1,42 @@
+getClassName();
+ }
+ public function checkUser(array $formDatas): UserEntity
+ {
+ if (!isset($formDatas['id']) || !$formDatas['id']) {
+ throw new \Exception("사용자ID를 입력해주세요!");
+ }
+ if (!isset($formDatas['passwd']) || !$formDatas['passwd']) {
+ throw new \Exception("암호를 입력해주세요!");
+ }
+
+ $entity = $this->getModel()->getEntityByID($formDatas['id']);
+ if (is_null($entity) || !isset($entity->passwd)) {
+ throw new \Exception("사용자ID: {$formDatas['id']}가 존재하지 않습니다.");
+ }
+
+ if (!password_verify($formDatas['passwd'], $entity->passwd)) {
+ // log_message("error", "암호: {$formDatas['passwd']}, {$entity->passwd}");
+ throw new \Exception("암호가 맞지 않습니다.");
+ }
+
+ return $entity;
+ }
+}
diff --git a/app/Services/CommonService.php b/app/Services/CommonService.php
new file mode 100644
index 0000000..50eba9d
--- /dev/null
+++ b/app/Services/CommonService.php
@@ -0,0 +1,148 @@
+_serviceDatas)) {
+ return null;
+ }
+ return $this->_serviceDatas[$name];
+ }
+ final public function __set($name, $value): void
+ {
+ $this->_serviceDatas[$name] = $value;
+ }
+
+ final public function getModel(): mixed
+ {
+ if ($this->_model === null) {
+ $modelClass = $this->getModelClass();
+ $this->_model = new $modelClass();
+ // $this->_model->setDebug(true);
+ }
+ return $this->_model;
+ }
+ final public function getEntity(): mixed
+ {
+ $result = $this->getModel()->get();
+ if (!$result) { //결과값이 없으면 null
+ return $result;
+ }
+ $entityClass = $this->getEntityClass();
+ return new $entityClass($result);
+ }
+ final public function getEntities(): array
+ {
+ $entitys = [];
+ foreach ($this->getModel()->getAll() as $result) {
+ $entityClass = $this->getEntityClass();
+ $entity = new $entityClass($result);
+ $pairField = $this->getModel()->getPairField();
+ // echo "pairField:" . $pairField . " ";
+ $entitys[$entity->$pairField] = $entity;
+ }
+ return $entitys;
+ } //
+ final public function getCount(string $select = "COUNT(*) as cnt", string $column = 'cnt'): int
+ {
+ return $this->getModel()->count($select, $column)->countAllResults();
+ }
+ //List 조건절 처리
+ private function setConditionForList(IncomingRequest $request, array $filter_fields): void
+ {
+ //조건절 처리
+ foreach ($filter_fields as $field) {
+ $this->$field = $request->getVar($field) ?? DEFAULTS['EMPTY'];
+ if ($this->$field !== DEFAULTS['EMPTY']) {
+ $this->getModel()->setList_FieldFilter($field, $this->$field);
+ }
+ }
+ //검색어 처리
+ $this->word = $request->getVar('word') ?? DEFAULTS['EMPTY'];
+ if ($this->word !== DEFAULTS['EMPTY']) {
+ $this->getModel()->setList_WordFilter($this->word);
+ }
+ //검색일 처리
+ $this->start = $request->getVar('start') ?? DEFAULTS['EMPTY'];
+ $this->end = $request->getVar('end') ?: DEFAULTS['EMPTY'];
+ $this->getModel()->setList_DateFilter($this->start, $this->end);
+ }
+ //PageNation 처리
+ protected function setPageOptionsByPaginationForList(): void
+ {
+ $this->page_options = array("" => "줄수선택");
+ for ($i = $this->per_page; $i <= $this->total_count; $i += $this->per_page) {
+ $this->page_options[$i] = $i;
+ }
+ $this->page_options[$this->total_count] = $this->total_count;
+ }
+ protected function setPaginationForList(IncomingRequest $request, $pager_group = 'default', int $segment = 0, $template = 'bootstrap_full'): void
+ {
+ //Page, Per_page필요부분
+ $this->page = (int) $request->getVar('page') ?: 1;
+ $this->per_page = (int) $request->getVar('per_page') ?: intval(DEFAULT_LIST_PERPAGE ?? 20);
+ //줄수 처리용
+ $this->setPageOptionsByPaginationForList();
+ // 1.Views/Pagers/에 bootstrap_full.php,bootstrap_simple.php 생성
+ // 2.app/Config/Pager.php/$templates에 'bootstrap_full => 'Pagers\bootstrap_full',
+ // 'bootstrap_simple' => 'Pagers\bootstrap_simple', 추가
+ $pager = service("pager");
+ // $this->getService()->getModel()->paginate($this->per_page, $pager_group, $this->page, $segment);
+ $pager->makeLinks($this->page, $this->per_page, $this->total_count, $template, $segment, $pager_group);
+ $this->page = $pager->getCurrentPage($pager_group);
+ $this->total_page = $pager->getPageCount($pager_group);
+ $this->pagination - $pager->links($pager_group, $template);
+ }
+ protected function setOrderByForList(): void
+ {
+ $this->order_field = $this->request->getVar('order_field') ?: DEFAULTS['EMPTY'];
+ $this->order_value = $this->request->getVar('order_value') ?: DEFAULTS['EMPTY'];
+ if ($this->order_field !== DEFAULTS['EMPTY'] && $this->order_value !== DEFAULTS['EMPTY']) {
+ $this->getModel()->orderBy(sprintf("%s.%s %s", $this->getModel()::TABLE, $this->order_field, $this->order_value));
+ } else {
+ $this->getModel()->orderBy(sprintf("%s.%s %s", $this->getModel()::TABLE, $this->getModel()::PK, "DESC"));
+ }
+ }
+ final public function getList(IncomingRequest $request, array $fields, array $filter_fields): array
+ {
+ //조건절 처리
+ $this->setConditionForList($request, $filter_fields);
+ //TotalCount
+ $this->total_count = $this->getCount();
+ //Pagination 처리
+ $this->setPaginationForList($request);
+ //limit, offset 설정
+ $this->getModel()->limit($this->per_page);
+ $this->getModel()->offset(($this->page - 1) * $this->per_page);
+ return $this->getEntities();
+ }
+ final public function isIPAddress(string $ip, $type = false): bool
+ {
+ switch ($type) {
+ case 'ipv4':
+ $result = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
+ break;
+ case 'ipv6':
+ $result = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
+ break;
+ case 'all':
+ $result = filter_var($ip, FILTER_VALIDATE_IP);
+ break;
+ default:
+ $result = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
+ break;
+ }
+ return $result;
+ }
+}
diff --git a/app/Services/MapurlService.php b/app/Services/MapurlService.php
new file mode 100644
index 0000000..1e04fd3
--- /dev/null
+++ b/app/Services/MapurlService.php
@@ -0,0 +1,51 @@
+getClassName();
+ }
+ public function getModel(): MapurlModel
+ {
+ if ($this->_model === null) {
+ $this->_model = new MapurlModel();
+ }
+ return $this->_model;
+ }
+ public function create(array $formDatas): MapurlEntity
+ {
+ $entity = $this->getModel()->create($formDatas);
+ //생성값 formDatas Log남기기
+ $this->add_MylogTrait(__FUNCTION__, $formDatas, $entity);
+ return $entity;
+ }
+ public function modify(MapurlEntity $entity, array $formDatas): MapurlEntity
+ {
+ //변경전 entity 값, 변경값 formDatas Log남기기
+ $this->add_MylogTrait(__FUNCTION__, $formDatas, $entity);
+ return $this->getModel()->modify($entity, $formDatas);
+ }
+ public function delete(MapurlEntity $entity): void
+ {
+ //삭제전 entity 값 Log남기기
+ $this->add_MylogTrait(__FUNCTION__, [], $entity);
+ $this->getModel()->delete($entity->getPK());
+ }
+}
diff --git a/app/Services/MyLogService.php b/app/Services/MyLogService.php
new file mode 100644
index 0000000..847ffe6
--- /dev/null
+++ b/app/Services/MyLogService.php
@@ -0,0 +1,49 @@
+getClassName();
+ }
+ static public function getModel(): Model
+ {
+ if (self::$_model === null) {
+ self::$_model = new Model();
+ }
+ return self::$_model;
+ }
+ static public function add(string $level, string $message): void
+ {
+ self::$_logBuffers[] = sprintf("%s[%s]: %s", date("H:i:s"), $level, $message);
+ log_message($level, $message);
+ }
+
+ static public function save($service, string $method, AuthService $myauth, string $title, string $status = "unuse"): Entity
+ {
+ $formDatas = [
+ 'user_uid' => $myauth->getUIDByAuthInfo(),
+ 'class_name' => $service->getClassName(),
+ 'method_name' => $method,
+ 'title' => $title,
+ 'content' => implode("\n", self::$_logBuffers),
+ 'status' => $status,
+ ];
+ self::$_logBuffers = [];
+ // dd($formDatas);
+ return self::getModel()->create($formDatas, new Entity());
+ }
+}
diff --git a/app/Services/UserService.php b/app/Services/UserService.php
new file mode 100644
index 0000000..0864084
--- /dev/null
+++ b/app/Services/UserService.php
@@ -0,0 +1,54 @@
+getClassName();
+ }
+ public function getModel(): UserModel
+ {
+ if ($this->_model === null) {
+ $this->_model = new UserModel();
+ }
+ return $this->_model;
+ }
+ public function create(array $formDatas): UserEntity
+ {
+ $formDatas['role'] = implode(DEFAULTS["DELIMITER_ROLE"], $formDatas['role']);
+ return $this->getModel()->create($formDatas);
+ }
+ public function modify(UserEntity $entity, array $formDatas): UserEntity
+ {
+ // die(var_export($formDatas, true));
+ //암호를 입력하지 않았을시는 변경하기 않게 하기위함
+ if (isset($formDatas['passwd']) && $formDatas['passwd'] == "") {
+ unset($formDatas['passwd']);
+ unset($formDatas['confirmpassword']);
+ }
+ //Role을 지정이 있을경우에만 , toggle이나 batcjhjob에서는 없을수도 있으므로
+ if (isset($formDatas['role'])) {
+ $formDatas['role'] = implode(DEFAULTS["DELIMITER_ROLE"], $formDatas['role']);
+ }
+ // die(var_export($formDatas, true));
+ return $this->getModel()->modify($entity, $formDatas);
+ }
+ public function delete(UserEntity $entity): void
+ {
+ $this->getModel()->delete($entity);
+ }
+}
diff --git a/app/ThirdParty/.gitkeep b/app/ThirdParty/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/app/Traits/FileTrait.php b/app/Traits/FileTrait.php
new file mode 100644
index 0000000..7b94976
--- /dev/null
+++ b/app/Traits/FileTrait.php
@@ -0,0 +1,62 @@
+isFileType_FileTrait($file_ext, $type)) {
+ $files[] = $file_name;
+ }
+ }
+ return $files;
+ }
+
+ final protected function getUniqueName_FileTrait(string $path, string $file_name): string
+ {
+ $fileExtension = pathinfo($file_name, PATHINFO_EXTENSION);
+ $fileBaseName = pathinfo($file_name, PATHINFO_FILENAME);
+ $newFilename = $file_name;
+ $counter = 1;
+ // 중복된 파일명이 존재하는지 확인
+ while (file_exists($path . DIRECTORY_SEPARATOR . $newFilename)) {
+ // 중복된 파일명이 존재하면 숫자를 추가하여 새로운 파일명 생성
+ $newFilename = $fileBaseName . '_' . $counter . '.' . $fileExtension;
+ $counter++;
+ }
+ return $newFilename;
+ }
+}
diff --git a/app/Traits/ImageTrait.php b/app/Traits/ImageTrait.php
new file mode 100644
index 0000000..5fe05e3
--- /dev/null
+++ b/app/Traits/ImageTrait.php
@@ -0,0 +1,114 @@
+_image);
+ }
+
+ // 이미지의 현재 높이를 반환하는 메소드
+ final public function getHeight_ImageTrait()
+ {
+ return imagesy($this->_image);
+ }
+
+ // 이미지 파일을 로드하는 메소드
+ final public function load_ImageTrait($file)
+ {
+ $imageInfo = getimagesize($file);
+ $this->_imageType = $imageInfo[2];
+ switch ($this->_imageType) {
+ case IMAGETYPE_JPEG:
+ $this->_image = imagecreatefromjpeg($file);
+ break;
+ case IMAGETYPE_GIF:
+ $this->_image = imagecreatefromgif($file);
+ break;
+ case IMAGETYPE_PNG:
+ $this->_image = imagecreatefrompng($file);
+ break;
+ case IMAGETYPE_WEBP:
+ $this->_image = imagecreatefromwebp($file);
+ break;
+ default:
+ throw new \Exception("Unsupported image type: " . $this->_imageType);
+ }
+ }
+
+ // 이미지 크기를 지정된 너비, 높이로 변경하는 메소드
+ final public function resize_ImageTrait($width, $height)
+ {
+ $newImage = imagecreatetruecolor($width, $height);
+ imagecopyresampled(
+ $newImage,
+ $this->_image,
+ 0,
+ 0,
+ 0,
+ 0,
+ $width,
+ $height,
+ $this->getWidth_ImageTrait(),
+ $this->getHeight_ImageTrait()
+ );
+ $this->_image = $newImage;
+ }
+
+ // 이미지 비율을 유지하면서 크기를 조정하는 메소드
+ final public function resizeToWidth_ImageTrait($width)
+ {
+ $ratio = $width / $this->getWidth_ImageTrait();
+ $height = $this->getHeight_ImageTrait() * $ratio;
+ $this->resize_ImageTrait($width, $height);
+ }
+
+ final public function resizeToHeight_ImageTrait($height)
+ {
+ $ratio = $height / $this->getHeight_ImageTrait();
+ $width = $this->getWidth_ImageTrait() * $ratio;
+ $this->resize_ImageTrait($width, $height);
+ }
+
+ final public function scale($scale)
+ {
+ $width = $this->getWidth_ImageTrait() * ($scale / 100);
+ $height = $this->getHeight_ImageTrait() * ($scale / 100);
+ $this->resize_ImageTrait($width, $height);
+ }
+
+ // 이미지를 저장하는 메소드
+ final public function save_ImageTrait($file, $imageType = IMAGETYPE_WEBP, $compression = 75)
+ {
+ switch ($imageType) {
+ case IMAGETYPE_JPEG:
+ imagejpeg($this->_image, $file, $compression);
+ break;
+ case IMAGETYPE_GIF:
+ imagegif($this->_image, $file);
+ break;
+ case IMAGETYPE_PNG:
+ imagepng($this->_image, $file);
+ break;
+ case IMAGETYPE_WEBP:
+ imagewebp($this->_image, $file, $compression);
+ break;
+ default:
+ throw new \Exception("Unsupported image type: " . $imageType);
+ }
+ }
+
+ // 메모리 해제를 위한 메소드
+ final public function destroy_ImageTrait()
+ {
+ imagedestroy($this->_image);
+ }
+}
diff --git a/app/Traits/MylogTrait.php b/app/Traits/MylogTrait.php
new file mode 100644
index 0000000..5e1c34b
--- /dev/null
+++ b/app/Traits/MylogTrait.php
@@ -0,0 +1,34 @@
+ $value) {
+ MyLogService::add("info", "{$field}:{$entity->$field}");
+ }
+ MyLogService::add("info", "{$entity->getTitle()}를 생성하였습니다.");
+ break;
+ case 'modify':
+ foreach ($formDatas as $field => $value) {
+ MyLogService::add("info", "{$field}:{$entity->$field}=>{$value}");
+ }
+ MyLogService::add("info", "{$entity->getTitle()}를 수정하였습니다.");
+ break;
+ case 'delete':
+ foreach ($this->getModel()->getFields() as $field) {
+ MyLogService::add("info", "{$field}:{$entity->$field}");
+ }
+ MyLogService::add("info", "{$entity->getTitle()}를 삭제하였습니다.");
+ break;
+ }
+ }
+}
diff --git a/app/Views/Pagers/bootstrap_full.php b/app/Views/Pagers/bootstrap_full.php
new file mode 100644
index 0000000..49af876
--- /dev/null
+++ b/app/Views/Pagers/bootstrap_full.php
@@ -0,0 +1,35 @@
+setSurroundCount(2);
+?>
+
+
+
\ No newline at end of file
diff --git a/app/Views/Pagers/bootstrap_simple.php b/app/Views/Pagers/bootstrap_simple.php
new file mode 100644
index 0000000..e728703
--- /dev/null
+++ b/app/Views/Pagers/bootstrap_simple.php
@@ -0,0 +1,17 @@
+setSurroundCount(0);
+?>
+
+
+
\ No newline at end of file
diff --git a/app/Views/admin/create.php b/app/Views/admin/create.php
new file mode 100644
index 0000000..f567529
--- /dev/null
+++ b/app/Views/admin/create.php
@@ -0,0 +1,29 @@
+= $this->extend(LAYOUTS[$viewDatas['layout']]['path']) ?>
+= $this->section('content') ?>
+
+
+ = form_open(current_url(), $viewDatas['forms']['attributes'], $viewDatas['forms']['hiddens']) ?>
+
+
+
+= $this->endSection() ?>
\ No newline at end of file
diff --git a/app/Views/admin/index.php b/app/Views/admin/index.php
new file mode 100644
index 0000000..6fd1af7
--- /dev/null
+++ b/app/Views/admin/index.php
@@ -0,0 +1,62 @@
+= $this->extend(LAYOUTS[$viewDatas['layout']]['path']) ?>
+= $this->section('content') ?>
+= $this->include(LAYOUTS[$viewDatas['layout']]['path'] . '/top'); ?>
+
+
+
+
+
+ = $this->include(LAYOUTS[$viewDatas['layout']]['path'] . '/left_menu'); ?>
+
+
+
+
+ = $this->include("templates/{$viewDatas['layout']}/index_header"); ?>
+
+
+
+
+ = $this->include("templates/{$viewDatas['layout']}/index_content_top"); ?>
+
+
+
+ 번호
+
+ = $viewDatas['helper']->getListLabel($field, $viewDatas) ?>
+
+ 작업
+
+
+
+
+
+ getListRowColor($entity) ?>>
+
+
+ = $viewDatas['helper']->getListButton('modify', $viewDatas) ?>
+
+ = $viewDatas['helper']->getFieldView($field, $viewDatas) ?>
+
+ = $viewDatas['helper']->getListButton('delete', $viewDatas) ?>
+
+
+
+
+
+ = $this->include("templates/{$viewDatas['layout']}/index_content_batchjob"); ?>
+
+
+
= $this->include("templates/common/modal_fetch"); ?>
+
+
+
+
+
+
+
+
+
+
+ = $this->include(LAYOUTS[$viewDatas['layout']]['path'] . '/bottom'); ?>
+
+= $this->endSection() ?>
\ No newline at end of file
diff --git a/app/Views/admin/modify.php b/app/Views/admin/modify.php
new file mode 100644
index 0000000..db319ea
--- /dev/null
+++ b/app/Views/admin/modify.php
@@ -0,0 +1,28 @@
+= $this->extend(LAYOUTS[$viewDatas['layout']]['path']) ?>
+= $this->section('content') ?>
+
+
+ = form_open(current_url(), ['id' => 'action_form', ...$viewDatas['forms']['attributes']], $viewDatas['forms']['hiddens']) ?>
+
+
+= $this->endSection() ?>
\ No newline at end of file
diff --git a/app/Views/admin/view.php b/app/Views/admin/view.php
new file mode 100644
index 0000000..91ac599
--- /dev/null
+++ b/app/Views/admin/view.php
@@ -0,0 +1,19 @@
+= $this->extend(LAYOUTS[$viewDatas['layout']]['path']) ?>
+= $this->section('content') ?>
+
+= $this->endSection() ?>
\ No newline at end of file
diff --git a/app/Views/admin/welcome_message.php b/app/Views/admin/welcome_message.php
new file mode 100644
index 0000000..4b6614f
--- /dev/null
+++ b/app/Views/admin/welcome_message.php
@@ -0,0 +1,103 @@
+= $this->extend(LAYOUTS[$viewDatas['layout']]['path']) ?>
+= $this->section('content') ?>
+= $this->include(LAYOUTS[$viewDatas['layout']]['path'] . '/top'); ?>
+
+
+
+
+
+ = $this->include(LAYOUTS[$viewDatas['layout']]['path'] . '/left_menu'); ?>
+
+
+
+
+ = $this->include("templates/{$viewDatas['layout']}/index_header"); ?>
+
+
+
+
+
+
+
+= $this->include(LAYOUTS[$viewDatas['layout']]['path'] . '/bottom'); ?>
+= $this->endSection() ?>
\ No newline at end of file
diff --git a/app/Views/errors/cli/error_404.php b/app/Views/errors/cli/error_404.php
new file mode 100644
index 0000000..456ea3e
--- /dev/null
+++ b/app/Views/errors/cli/error_404.php
@@ -0,0 +1,7 @@
+getFile()) . ':' . $exception->getLine(), 'green'));
+CLI::newLine();
+
+$last = $exception;
+
+while ($prevException = $last->getPrevious()) {
+ $last = $prevException;
+
+ CLI::write(' Caused by:');
+ CLI::write(' [' . $prevException::class . ']', 'red');
+ CLI::write(' ' . $prevException->getMessage());
+ CLI::write(' at ' . CLI::color(clean_path($prevException->getFile()) . ':' . $prevException->getLine(), 'green'));
+ CLI::newLine();
+}
+
+// The backtrace
+if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) {
+ $backtraces = $last->getTrace();
+
+ if ($backtraces) {
+ CLI::write('Backtrace:', 'green');
+ }
+
+ foreach ($backtraces as $i => $error) {
+ $padFile = ' '; // 4 spaces
+ $padClass = ' '; // 7 spaces
+ $c = str_pad($i + 1, 3, ' ', STR_PAD_LEFT);
+
+ if (isset($error['file'])) {
+ $filepath = clean_path($error['file']) . ':' . $error['line'];
+
+ CLI::write($c . $padFile . CLI::color($filepath, 'yellow'));
+ } else {
+ CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow'));
+ }
+
+ $function = '';
+
+ if (isset($error['class'])) {
+ $type = ($error['type'] === '->') ? '()' . $error['type'] : $error['type'];
+ $function .= $padClass . $error['class'] . $type . $error['function'];
+ } elseif (! isset($error['class']) && isset($error['function'])) {
+ $function .= $padClass . $error['function'];
+ }
+
+ $args = implode(', ', array_map(static fn ($value) => match (true) {
+ is_object($value) => 'Object(' . $value::class . ')',
+ is_array($value) => count($value) ? '[...]' : '[]',
+ $value === null => 'null', // return the lowercased version
+ default => var_export($value, true),
+ }, array_values($error['args'] ?? [])));
+
+ $function .= '(' . $args . ')';
+
+ CLI::write($function);
+ CLI::newLine();
+ }
+}
diff --git a/app/Views/errors/cli/production.php b/app/Views/errors/cli/production.php
new file mode 100644
index 0000000..7db744e
--- /dev/null
+++ b/app/Views/errors/cli/production.php
@@ -0,0 +1,5 @@
+
+
+
+
+ = lang('Errors.pageNotFound') ?>
+
+
+
+
+
+
404
+
+
+
+ = nl2br(esc($message)) ?>
+
+ = lang('Errors.sorryCannotFind') ?>
+
+
+
+
+
diff --git a/app/Views/errors/html/error_exception.php b/app/Views/errors/html/error_exception.php
new file mode 100644
index 0000000..44d7498
--- /dev/null
+++ b/app/Views/errors/html/error_exception.php
@@ -0,0 +1,430 @@
+
+
+
+
+
+
+
+ = esc($title) ?>
+
+
+
+
+
+
+
+
+
+
+
+
= esc(clean_path($file)) ?> at line = esc($line) ?>
+
+
+
+ = static::highlightFile($file, $line, 15); ?>
+
+
+
+
+
+ getPrevious()) {
+ $last = $prevException;
+ ?>
+
+
+ Caused by:
+ = esc($prevException::class), esc($prevException->getCode() ? ' #' . $prevException->getCode() : '') ?>
+
+ = nl2br(esc($prevException->getMessage())) ?>
+ getMessage())) ?>"
+ rel="noreferrer" target="_blank">search →
+ = esc(clean_path($prevException->getFile()) . ':' . $prevException->getLine()) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $row) : ?>
+
+
+
+
+
+
+
+ {PHP internal code}
+
+
+
+
+ — = esc($row['class'] . $row['type'] . $row['function']) ?>
+
+
+ ( arguments )
+
+
+
+ getParameters();
+ }
+
+ foreach ($row['args'] as $key => $value) : ?>
+
+ = esc(isset($params[$key]) ? '$' . $params[$key]->name : "#{$key}") ?>
+ = esc(print_r($value, true)) ?>
+
+
+
+
+
+
+ ()
+
+
+
+
+ — = esc($row['function']) ?>()
+
+
+
+
+
+
+ = static::highlightFile($row['file'], $row['line']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
$= esc($var) ?>
+
+
+
+
+ Key
+ Value
+
+
+
+ $value) : ?>
+
+ = esc($key) ?>
+
+
+ = esc($value) ?>
+
+ = esc(print_r($value, true)) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
Constants
+
+
+
+
+ Key
+ Value
+
+
+
+ $value) : ?>
+
+ = esc($key) ?>
+
+
+ = esc($value) ?>
+
+ = esc(print_r($value, true)) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Path
+ = esc($request->getUri()) ?>
+
+
+ HTTP Method
+ = esc($request->getMethod()) ?>
+
+
+ IP Address
+ = esc($request->getIPAddress()) ?>
+
+
+ Is AJAX Request?
+ = $request->isAJAX() ? 'yes' : 'no' ?>
+
+
+ Is CLI Request?
+ = $request->isCLI() ? 'yes' : 'no' ?>
+
+
+ Is Secure Request?
+ = $request->isSecure() ? 'yes' : 'no' ?>
+
+
+ User Agent
+ = esc($request->getUserAgent()->getAgentString()) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
$= esc($var) ?>
+
+
+
+
+ Key
+ Value
+
+
+
+ $value) : ?>
+
+ = esc($key) ?>
+
+
+ = esc($value) ?>
+
+ = esc(print_r($value, true)) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ No $_GET, $_POST, or $_COOKIE Information to show.
+
+
+
+
+ headers(); ?>
+
+
+
Headers
+
+
+
+
+ Header
+ Value
+
+
+
+ $value) : ?>
+
+ = esc($name, 'html') ?>
+
+ getValueLine(), 'html');
+ } else {
+ foreach ($value as $i => $header) {
+ echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
+ }
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+ setStatusCode(http_response_code());
+ ?>
+
+
+
+ Response Status
+ = esc($response->getStatusCode() . ' - ' . $response->getReasonPhrase()) ?>
+
+
+
+ headers(); ?>
+
+
Headers
+
+
+
+
+ Header
+ Value
+
+
+
+ $value) : ?>
+
+ = esc($name, 'html') ?>
+
+ getHeaderLine($name), 'html');
+ } else {
+ foreach ($value as $i => $header) {
+ echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
+ }
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = esc(clean_path($file)) ?>
+
+
+
+
+
+
+
+
+
+
+ Memory Usage
+ = esc(static::describeMemory(memory_get_usage(true))) ?>
+
+
+ Peak Memory Usage:
+ = esc(static::describeMemory(memory_get_peak_usage(true))) ?>
+
+
+ Memory Limit:
+ = esc(ini_get('memory_limit')) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/Views/errors/html/production.php b/app/Views/errors/html/production.php
new file mode 100644
index 0000000..2f59a8d
--- /dev/null
+++ b/app/Views/errors/html/production.php
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ = lang('Errors.whoops') ?>
+
+
+
+
+
+
+
+
= lang('Errors.whoops') ?>
+
+
= lang('Errors.weHitASnag') ?>
+
+
+
+
+
+
diff --git a/app/Views/layouts/admin.php b/app/Views/layouts/admin.php
new file mode 100644
index 0000000..21dbb89
--- /dev/null
+++ b/app/Views/layouts/admin.php
@@ -0,0 +1,30 @@
+
+
+
+
+ = LAYOUTS[$viewDatas['layout']]['title'] ?>
+
+ = $meta ?>
+
+
+ = $stylesheet ?>
+
+
+ = $javascript ?>
+
+
+
+
+
+
+
+
+
+ = $viewDatas['helper']->alert($error) ?>
+ = $this->renderSection('content') ?>
+
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/admin/bottom.php b/app/Views/layouts/admin/bottom.php
new file mode 100644
index 0000000..785e0cd
--- /dev/null
+++ b/app/Views/layouts/admin/bottom.php
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/admin/left_menu.php b/app/Views/layouts/admin/left_menu.php
new file mode 100644
index 0000000..c64f8df
--- /dev/null
+++ b/app/Views/layouts/admin/left_menu.php
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/admin/left_menu/base.php b/app/Views/layouts/admin/left_menu/base.php
new file mode 100644
index 0000000..b895b2a
--- /dev/null
+++ b/app/Views/layouts/admin/left_menu/base.php
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/admin/left_menu/cloudflare.php b/app/Views/layouts/admin/left_menu/cloudflare.php
new file mode 100644
index 0000000..511db6b
--- /dev/null
+++ b/app/Views/layouts/admin/left_menu/cloudflare.php
@@ -0,0 +1,26 @@
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/admin/left_menu/site.php b/app/Views/layouts/admin/left_menu/site.php
new file mode 100644
index 0000000..1bf5b18
--- /dev/null
+++ b/app/Views/layouts/admin/left_menu/site.php
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/admin/top.php b/app/Views/layouts/admin/top.php
new file mode 100644
index 0000000..9396ef5
--- /dev/null
+++ b/app/Views/layouts/admin/top.php
@@ -0,0 +1,50 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/empty.php b/app/Views/layouts/empty.php
new file mode 100644
index 0000000..ce76d5b
--- /dev/null
+++ b/app/Views/layouts/empty.php
@@ -0,0 +1,30 @@
+
+
+
+
+ = LAYOUTS[$viewDatas['layout']]['title'] ?>
+
+ = $meta ?>
+
+
+ = $stylesheet ?>
+
+
+ = $javascript ?>
+
+
+
+
+
+
+
+
+
+ = $viewDatas['helper']->alert($error) ?>
+ = $this->renderSection('content') ?>
+
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/front.php b/app/Views/layouts/front.php
new file mode 100644
index 0000000..33be8ea
--- /dev/null
+++ b/app/Views/layouts/front.php
@@ -0,0 +1,30 @@
+
+
+
+
+ = LAYOUTS[$viewDatas['layout']]['title'] ?>
+
+ = $meta ?>
+
+
+ = $stylesheet ?>
+
+
+ = $javascript ?>
+
+
+
+
+
+
+
+
+
+ = $viewDatas['helper']->alert($error) ?>
+ = $this->renderSection('content') ?>
+
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/front/bottom.php b/app/Views/layouts/front/bottom.php
new file mode 100644
index 0000000..785e0cd
--- /dev/null
+++ b/app/Views/layouts/front/bottom.php
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/front/left_menu.php b/app/Views/layouts/front/left_menu.php
new file mode 100644
index 0000000..e920e68
--- /dev/null
+++ b/app/Views/layouts/front/left_menu.php
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/app/Views/layouts/front/top.php b/app/Views/layouts/front/top.php
new file mode 100644
index 0000000..3498bcf
--- /dev/null
+++ b/app/Views/layouts/front/top.php
@@ -0,0 +1,41 @@
+
+
+
+
사용자페이지
+
+
+ = ICONS['LOCK'] ?>
+
+
+
+
+
+ isLoggedIn()): ?>
+
+
+ = ICONS['LOGIN'] . $viewDatas['myauth']->getNameByAuthInfo('name') ?>
+
+
+
+ = ICONS['LOGIN'] ?>Login
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/login.php b/app/Views/login.php
new file mode 100644
index 0000000..395d19f
--- /dev/null
+++ b/app/Views/login.php
@@ -0,0 +1,24 @@
+= $viewDatas['helper']->alert($error) ?>
+
+
+
+
+
CF-MGR 로그인
+ = form_open(current_url(), $viewDatas['forms']['attributes'], $viewDatas['forms']['hiddens']) ?>
+
+ 아이디
+
+
+
+ 비밀번호
+
+
+
로그인
+
+ = anchor($viewDatas['google_url'], ICONS['GOOGLE'] . 'Google 로그인', ["class" => "btn-google"]) ?>
+ Facebook
+
+ = form_close(); ?>
+
+
+
\ No newline at end of file
diff --git a/app/Views/templates/admin/index_content_batchjob.php b/app/Views/templates/admin/index_content_batchjob.php
new file mode 100644
index 0000000..6284143
--- /dev/null
+++ b/app/Views/templates/admin/index_content_batchjob.php
@@ -0,0 +1,56 @@
+
+ = form_open("", ['id' => 'batchjob_form', 'method' => "post"]) ?>
+
+
+ = form_checkbox(array("id" => "batchjobuids_checkbox")) ?>ALL
+
+ = $viewDatas['helper']->getFieldForm($field, DEFAULTS['EMPTY'], $viewDatas, ['data-batchjob' => 'true']) ?>
+
+ = $viewDatas['helper']->getListButton('batchjob', $viewDatas) ?>
+ = $viewDatas['helper']->getListButton('create', $viewDatas) ?>
+ = $viewDatas['helper']->getListButton('batchjob_delete', $viewDatas) ?>
+
+ = form_close() ?>
+
+
\ No newline at end of file
diff --git a/app/Views/templates/admin/index_content_top.php b/app/Views/templates/admin/index_content_top.php
new file mode 100644
index 0000000..5a8e551
--- /dev/null
+++ b/app/Views/templates/admin/index_content_top.php
@@ -0,0 +1,23 @@
+ = form_open(current_url(), ["method" => "get"]) ?>
+
+
+
+ 조건:
+
+ = $viewDatas['helper']->getFieldForm($field, $viewDatas[$field] ?: old($field), $viewDatas) ?>
+
+
+
+ 검색어:= form_input('word', $viewDatas['word']) ?>
+ 검색일:= form_input('start', $viewDatas['start'], ["class" => "calender"]) ?>= form_input('end', $viewDatas['end'], ["class" => "calender"]) ?>
+ 검색
+ = anchor(current_url() . '/download/excel', ICONS['EXCEL'], ["target" => "_self", "class" => "excel"]) ?>
+
+
+ Page:= $viewDatas['page'] ?>/= $viewDatas['total_page'] ?>
+ = form_dropdown('per_page', $viewDatas['page_options'], $viewDatas['per_page'], ['onChange' => 'this.form.submit()']) ?>
+ / 총:= $viewDatas['total_count'] ?>
+
+
+
+ = form_close() ?>
\ No newline at end of file
diff --git a/app/Views/templates/admin/index_footer.php b/app/Views/templates/admin/index_footer.php
new file mode 100644
index 0000000..e69de29
diff --git a/app/Views/templates/admin/index_header.php b/app/Views/templates/admin/index_header.php
new file mode 100644
index 0000000..e4989ca
--- /dev/null
+++ b/app/Views/templates/admin/index_header.php
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/app/Views/templates/admin/index_header/dns.php b/app/Views/templates/admin/index_header/dns.php
new file mode 100644
index 0000000..8432b80
--- /dev/null
+++ b/app/Views/templates/admin/index_header/dns.php
@@ -0,0 +1,79 @@
+
\ No newline at end of file
diff --git a/app/Views/templates/common/modal_fetch.php b/app/Views/templates/common/modal_fetch.php
new file mode 100644
index 0000000..f14ab26
--- /dev/null
+++ b/app/Views/templates/common/modal_fetch.php
@@ -0,0 +1,168 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/templates/common/modal_iframe.php b/app/Views/templates/common/modal_iframe.php
new file mode 100644
index 0000000..312bd66
--- /dev/null
+++ b/app/Views/templates/common/modal_iframe.php
@@ -0,0 +1,71 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/templates/download.php b/app/Views/templates/download.php
new file mode 100644
index 0000000..3c52984
--- /dev/null
+++ b/app/Views/templates/download.php
@@ -0,0 +1,20 @@
+
+
+
+
+ = lang("{$viewDatas['class_path']}.label.{$field}") ?>
+
+
+
+
+
+
+
+
+ = $entity->$field ?>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/Views/templates/front/index_footer.php b/app/Views/templates/front/index_footer.php
new file mode 100644
index 0000000..e69de29
diff --git a/app/Views/templates/front/index_header.php b/app/Views/templates/front/index_header.php
new file mode 100644
index 0000000..e35fef7
--- /dev/null
+++ b/app/Views/templates/front/index_header.php
@@ -0,0 +1,87 @@
+
+
+
+ = ICONS['DESKTOP'] ?> = $viewDatas['title'] ?>
+
+
+
+ Cloudflare DNS
+
+
+
\ No newline at end of file
diff --git a/app/Views/welcome_message.php b/app/Views/welcome_message.php
new file mode 100644
index 0000000..c18eca3
--- /dev/null
+++ b/app/Views/welcome_message.php
@@ -0,0 +1,331 @@
+
+
+
+
+ Welcome to CodeIgniter 4!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ About this page
+
+ The page you are looking at is being generated dynamically by CodeIgniter.
+
+ If you would like to edit this page you will find it located at:
+
+ app/Views/welcome_message.php
+
+ The corresponding controller for this page can be found at:
+
+ app/Controllers/Home.php
+
+
+
+
+
+
+
+ Go further
+
+
+
+ Learn
+
+
+ The User Guide contains an introduction, tutorial, a number of "how to"
+ guides, and then reference documentation for the components that make up
+ the framework. Check the User Guide !
+
+
+
+ Discuss
+
+
+ CodeIgniter is a community-developed open source project, with several
+ venues for the community members to gather and exchange ideas. View all
+ the threads on CodeIgniter's forum , or chat on Slack !
+
+
+
+ Contribute
+
+
+ CodeIgniter is a community driven project and accepts contributions
+ of code and documentation from the community. Why not
+
+ join us ?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/index.html b/app/index.html
new file mode 100644
index 0000000..69df4e1
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,11 @@
+
+
+
+ 403 Forbidden
+
+
+
+Directory access is forbidden.
+
+
+
diff --git a/builds b/builds
new file mode 100644
index 0000000..cc2ca08
--- /dev/null
+++ b/builds
@@ -0,0 +1,125 @@
+#!/usr/bin/env php
+ 'vcs',
+ 'url' => GITHUB_URL,
+ ];
+ }
+
+ $array['require']['codeigniter4/codeigniter4'] = 'dev-develop';
+ unset($array['require']['codeigniter4/framework']);
+ } else {
+ unset($array['minimum-stability']);
+
+ if (isset($array['repositories'])) {
+ foreach ($array['repositories'] as $i => $repository) {
+ if ($repository['url'] === GITHUB_URL) {
+ unset($array['repositories'][$i]);
+ break;
+ }
+ }
+
+ if (empty($array['repositories'])) {
+ unset($array['repositories']);
+ }
+ }
+
+ $array['require']['codeigniter4/framework'] = LATEST_RELEASE;
+ unset($array['require']['codeigniter4/codeigniter4']);
+ }
+
+ file_put_contents($file, json_encode($array, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL);
+
+ $modified[] = $file;
+ } else {
+ echo 'Warning: Unable to decode composer.json! Skipping...' . PHP_EOL;
+ }
+ } else {
+ echo 'Warning: Unable to read composer.json! Skipping...' . PHP_EOL;
+ }
+}
+
+$files = [
+ __DIR__ . DIRECTORY_SEPARATOR . 'app/Config/Paths.php',
+ __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.xml.dist',
+ __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.xml',
+];
+
+foreach ($files as $file) {
+ if (is_file($file)) {
+ $contents = file_get_contents($file);
+
+ if ($dev) {
+ $contents = str_replace('vendor/codeigniter4/framework', 'vendor/codeigniter4/codeigniter4', $contents);
+ } else {
+ $contents = str_replace('vendor/codeigniter4/codeigniter4', 'vendor/codeigniter4/framework', $contents);
+ }
+
+ file_put_contents($file, $contents);
+
+ $modified[] = $file;
+ }
+}
+
+if ($modified === []) {
+ echo 'No files modified.' . PHP_EOL;
+} else {
+ echo 'The following files were modified:' . PHP_EOL;
+
+ foreach ($modified as $file) {
+ echo " * {$file}" . PHP_EOL;
+ }
+
+ echo 'Run `composer update` to sync changes with your vendor folder.' . PHP_EOL;
+}
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..4d161ea
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,53 @@
+{
+ "name": "codeigniter4/appstarter",
+ "description": "CodeIgniter4 starter app",
+ "license": "MIT",
+ "type": "project",
+ "homepage": "https://codeigniter.com",
+ "support": {
+ "forum": "https://forum.codeigniter.com/",
+ "source": "https://github.com/codeigniter4/CodeIgniter4",
+ "slack": "https://codeigniterchat.slack.com"
+ },
+ "require": {
+ "php": "^8.2",
+ "cloudflare/sdk": "^1.3",
+ "codeigniter4/framework": "^4.5",
+ "google/apiclient": "^2.15.0",
+ "guzzlehttp/guzzle": "^7.9",
+ "io-developer/php-whois": "^4.1",
+ "phpoffice/phpspreadsheet": "^1.27",
+ "symfony/css-selector": "^7.1",
+ "symfony/dom-crawler": "^7.1",
+ "tinymce/tinymce": "^7.3",
+ "twbs/bootstrap": "5.3.3"
+ },
+ "require-dev": {
+ "fakerphp/faker": "^1.9",
+ "mikey179/vfsstream": "^1.6",
+ "phpunit/phpunit": "^10.5.16"
+ },
+ "autoload": {
+ "psr-4": {
+ "App\\": "app/",
+ "Config\\": "app/Config/"
+ },
+ "exclude-from-classmap": [
+ "**/Database/Migrations/**"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\Support\\": "tests/_support"
+ }
+ },
+ "config": {
+ "optimize-autoloader": true,
+ "preferred-install": "dist",
+ "sort-packages": true,
+ "process-timeout": 600
+ },
+ "scripts": {
+ "test": "phpunit"
+ }
+}
diff --git a/env b/env
new file mode 100644
index 0000000..f359ec2
--- /dev/null
+++ b/env
@@ -0,0 +1,69 @@
+#--------------------------------------------------------------------
+# Example Environment Configuration file
+#
+# This file can be used as a starting point for your own
+# custom .env files, and contains most of the possible settings
+# available in a default install.
+#
+# By default, all of the settings are commented out. If you want
+# to override the setting, you must un-comment it by removing the '#'
+# at the beginning of the line.
+#--------------------------------------------------------------------
+
+#--------------------------------------------------------------------
+# ENVIRONMENT
+#--------------------------------------------------------------------
+
+# CI_ENVIRONMENT = production
+
+#--------------------------------------------------------------------
+# APP
+#--------------------------------------------------------------------
+
+# app.baseURL = ''
+# If you have trouble with `.`, you could also use `_`.
+# app_baseURL = ''
+# app.forceGlobalSecureRequests = false
+# app.CSPEnabled = false
+
+#--------------------------------------------------------------------
+# DATABASE
+#--------------------------------------------------------------------
+
+# database.default.hostname = localhost
+# database.default.database = ci4
+# database.default.username = root
+# database.default.password = root
+# database.default.DBDriver = MySQLi
+# database.default.DBPrefix =
+# database.default.port = 3306
+
+# If you use MySQLi as tests, first update the values of Config\Database::$tests.
+# database.tests.hostname = localhost
+# database.tests.database = ci4_test
+# database.tests.username = root
+# database.tests.password = root
+# database.tests.DBDriver = MySQLi
+# database.tests.DBPrefix =
+# database.tests.charset = utf8mb4
+# database.tests.DBCollat = utf8mb4_general_ci
+# database.tests.port = 3306
+
+#--------------------------------------------------------------------
+# ENCRYPTION
+#--------------------------------------------------------------------
+
+# encryption.key =
+
+#--------------------------------------------------------------------
+# SESSION
+#--------------------------------------------------------------------
+
+# session.driver = 'CodeIgniter\Session\Handlers\FileHandler'
+# session.savePath = null
+
+#--------------------------------------------------------------------
+# LOGGER
+#--------------------------------------------------------------------
+
+# logger.threshold = 4
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..b408a99
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ ./tests
+
+
+
+
+
+
+
+
+
+ ./app
+
+
+ ./app/Views
+ ./app/Config/Routes.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/preload.php b/preload.php
new file mode 100644
index 0000000..75d86f5
--- /dev/null
+++ b/preload.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view
+ * the LICENSE file that was distributed with this source code.
+ */
+
+/*
+ *---------------------------------------------------------------
+ * Sample file for Preloading
+ *---------------------------------------------------------------
+ * See https://www.php.net/manual/en/opcache.preloading.php
+ *
+ * How to Use:
+ * 0. Copy this file to your project root folder.
+ * 1. Set the $paths property of the preload class below.
+ * 2. Set opcache.preload in php.ini.
+ * php.ini:
+ * opcache.preload=/path/to/preload.php
+ */
+
+// Load the paths config file
+require __DIR__ . '/app/Config/Paths.php';
+
+// Path to the front controller
+define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR);
+
+class preload
+{
+ /**
+ * @var array Paths to preload.
+ */
+ private array $paths = [
+ [
+ 'include' => __DIR__ . '/vendor/codeigniter4/framework/system', // Change this path if using manual installation
+ 'exclude' => [
+ '/system/bootstrap.php',
+ // Not needed if you don't use them.
+ '/system/Database/OCI8/',
+ '/system/Database/Postgre/',
+ '/system/Database/SQLite3/',
+ '/system/Database/SQLSRV/',
+ // Not needed.
+ '/system/Database/Seeder.php',
+ '/system/Test/',
+ '/system/Language/',
+ '/system/CLI/',
+ '/system/Commands/',
+ '/system/Publisher/',
+ '/system/ComposerScripts.php',
+ '/Views/',
+ // Errors occur.
+ '/system/Config/Routes.php',
+ '/system/ThirdParty/',
+ ],
+ ],
+ ];
+
+ public function __construct()
+ {
+ $this->loadAutoloader();
+ }
+
+ private function loadAutoloader(): void
+ {
+ $paths = new Config\Paths();
+ require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'Boot.php';
+
+ CodeIgniter\Boot::preload($paths);
+ }
+
+ /**
+ * Load PHP files.
+ */
+ public function load(): void
+ {
+ foreach ($this->paths as $path) {
+ $directory = new RecursiveDirectoryIterator($path['include']);
+ $fullTree = new RecursiveIteratorIterator($directory);
+ $phpFiles = new RegexIterator(
+ $fullTree,
+ '/.+((? $file) {
+ foreach ($path['exclude'] as $exclude) {
+ if (str_contains($file[0], $exclude)) {
+ continue 2;
+ }
+ }
+
+ require_once $file[0];
+ echo 'Loaded: ' . $file[0] . "\n";
+ }
+ }
+ }
+}
+
+(new preload())->load();
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 0000000..abac3cb
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,49 @@
+# Disable directory browsing
+Options -Indexes
+
+# ----------------------------------------------------------------------
+# Rewrite engine
+# ----------------------------------------------------------------------
+
+# Turning on the rewrite engine is necessary for the following rules and features.
+# FollowSymLinks must be enabled for this to work.
+
+ Options +FollowSymlinks
+ RewriteEngine On
+
+ # If you installed CodeIgniter in a subfolder, you will need to
+ # change the following line to match the subfolder you need.
+ # http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
+ # RewriteBase /
+
+ # Redirect Trailing Slashes...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_URI} (.+)/$
+ RewriteRule ^ %1 [L,R=301]
+
+ # Rewrite "www.example.com -> example.com"
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
+ RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
+
+ # Checks to see if the user is attempting to access a valid file,
+ # such as an image or css document, if this isn't true it sends the
+ # request to the front controller, index.php
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^([\s\S]*)$ index.php/$1 [L,NC,QSA]
+
+ # Ensure Authorization header is passed along
+ RewriteCond %{HTTP:Authorization} .
+ RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+
+
+
+ # If we don't have mod_rewrite installed, all 404's
+ # can be sent to index.php, and everything works as normal.
+ ErrorDocument 404 index.php
+
+
+# Disable server signature start
+ServerSignature Off
+# Disable server signature end
diff --git a/public/css/admin.css b/public/css/admin.css
new file mode 100644
index 0000000..4576f3b
--- /dev/null
+++ b/public/css/admin.css
@@ -0,0 +1,81 @@
+/* ------------------------------------------------------------
+ * Name : admin.css
+ * Desc : Admin StyleSheet
+ * Created : 2016/9/11 Tri-aBility by Junheum,Choi
+ * Updated :
+ ------------------------------------------------------------ */
+* {
+ margin: 0px;
+ padding: 0px;
+ border: 0px;
+}
+
+html,
+body {
+ height: 100%;
+}
+
+div.layout_top {
+ height: 51px;
+ margin-bottom: 10px;
+ border-top: 1px solid gray;
+ border-bottom: 1px solid gray;
+ background-color: #e8e9ea;
+}
+
+div.layout_bottom {
+ height: 51px;
+ margin-top: 10px;
+ border-top: 1px solid gray;
+ border-bottom: 1px solid gray;
+ background-color: #efefef;
+ background-color: #e8e9ea;
+}
+
+table.layout_middle {
+ width: 100%;
+ /* border: 1px solid blue; */
+}
+
+table.layout_middle td.layout_left {
+ vertical-align: top;
+ /* border: 1px solid red; */
+}
+
+table.layout_middle td.layout_right {
+ padding-top: 5px;
+ padding-left: 23px;
+ padding-right: 5px;
+ padding-bottom: 5px;
+ /* overflow: auto; */
+ /* border: 1px solid blue; */
+}
+
+table.layout_middle td.layout_right div.layout_header {
+ /*content 헤더라인*/
+ height: 55px;
+ padding-top: 15px;
+ background-color: #e7e7e7;
+ border-top: 1px solid gray;
+ border-left: 1px solid gray;
+ border-right: 1px solid gray;
+ border-radius: 15px 15px 0px 0px;
+}
+
+table.layout_middle td.layout_right div.layout_header li.nav-item {}
+
+table.layout_middle td.layout_right div.layout_footer {
+ /*content 하단라인*/
+ height: 20px;
+ border-left: 1px solid gray;
+ border-right: 1px solid gray;
+ border-bottom: 1px solid gray;
+ border-radius: 0px 0px 15px 15px;
+}
+
+table.layout_middle td.layout_right div.layout_content {
+ /*content 부분*/
+ padding: 5px;
+ border-left: 1px solid gray;
+ border-right: 1px solid gray;
+}
\ No newline at end of file
diff --git a/public/css/admin/form.css b/public/css/admin/form.css
new file mode 100644
index 0000000..7440ad5
--- /dev/null
+++ b/public/css/admin/form.css
@@ -0,0 +1,28 @@
+/* create,modify,view 페이지용 */
+div.action_form {
+ /* 블록 요소로 변경 */
+ margin-left: auto;
+ /* 자동 여백을 이용한 가로 가운데 정렬 */
+ margin-right: auto;
+ /* 자동 여백을 이용한 가로 가운데 정렬 */
+ /* border: 1px solid blue; */
+ /* table-layout: fixed; 고정 레이아웃 */
+ border-collapse: collapse;
+ /* 테두리 결합 */
+}
+
+div.action_form table {}
+
+div.action_form table th {
+ background-color: #f0f0f0;
+}
+
+div.action_form table td {
+ text-align: center;
+ word-wrap: break-word;
+ /* 긴 단어 강제 줄바꿈 */
+ white-space: normal;
+ /* 자동 줄바꿈 */
+}
+
+/* create,modify,view 페이지용 */
\ No newline at end of file
diff --git a/public/css/admin/index.css b/public/css/admin/index.css
new file mode 100644
index 0000000..9dc021e
--- /dev/null
+++ b/public/css/admin/index.css
@@ -0,0 +1,165 @@
+/* create,modify,view 페이지용 */
+table.action_form {
+ /* 블록 요소로 변경 */
+ margin-left: auto;
+ /* 자동 여백을 이용한 가로 가운데 정렬 */
+ margin-right: auto;
+ /* 자동 여백을 이용한 가로 가운데 정렬 */
+ /* border: 1px solid blue; */
+ /* table-layout: fixed; 고정 레이아웃 */
+ border-collapse: collapse;
+ /* 테두리 결합 */
+}
+
+table.action_form th {
+ text-align: center;
+ background-color: #f5f5f5;
+}
+
+table.action_form td {
+ text-align: center;
+ word-wrap: break-word;
+ /* 긴 단어 강제 줄바꿈 */
+ white-space: normal;
+ /* 자동 줄바꿈 */
+}
+
+/* create,modify,view 페이지용 */
+
+/*조건검색*/
+nav.index_top nav.condition {
+ border-color: 1px solid red;
+}
+
+/*검색*/
+nav.index_top nav.search {
+ position: relative;
+ border-color: 1px solid red;
+}
+
+nav.index_top nav.search input[type="text"] {
+ width: 150px;
+}
+
+/*검색submit*/
+nav.index_top nav.search input[type="submit"] {
+ font-weight: bold;
+ width: 70px;
+ color: white;
+ background-color: #555555;
+}
+
+/*검색submit*/
+nav.index_top nav.search a.excel {
+ position: absolute;
+ top: -5px;
+ right: -45px;
+ /* border-color: 1px solid red; */
+}
+
+/*페이지정보*/
+nav.index_top nav.pageinfo {
+ font-weight: bold;
+ /* border-color: 1px solid red; */
+}
+
+/* Table 부분 */
+table.index_table {
+ width: 100%;
+ /* table-layout: fixed; */
+ border-collapse: collapse;
+}
+
+table.index_table thead th {
+ white-space: nowrap;
+ padding-top: 15px;
+ padding-bottom: 15px;
+ font-weight: bold;
+ border-top: 2px solid black;
+ border-bottom: 1px solid silver;
+ background-color: #f5f5f5;
+ text-align: center;
+ /* border:1px solid silver; */
+}
+
+table.index_table thead th.index_head_short_column {
+ width: 80px;
+}
+
+table.index_table thead th:active {
+ cursor: grabbing;
+}
+
+table.index_table tbody th {
+ text-align: center;
+ /* border:1px solid silver; */
+}
+
+div.index_batchjob {
+ padding-top: 15px;
+ text-align: center;
+ /* border: 1px solid red; */
+}
+
+div.index_batchjob ul.nav li.nav-item {
+ margin-left: 10px;
+ /* border: 1px solid red; */
+}
+
+div.index_pagination {
+ margin-top: 20px;
+ /* border: 1px solid red; */
+}
+
+div.index_pagination ul.pagination {
+ /* border: 1px solid green; */
+ width: fit-content;
+ /* UL의 너비를 내용에 맞춤 */
+ margin: 0 auto;
+ /* 좌우 마진을 자동으로 설정하여 중앙 배치 */
+ padding: 0;
+ list-style: none;
+ /* 기본 점 스타일 제거 (옵션) */
+}
+
+/* pager의 template가 default_full일경우 사용 */
+/* div.index_pagination ul.pagination li {
+ margin-left: 5px;
+}
+
+div.index_pagination ul.pagination li a {
+ padding: 5px 10px 5px 10px;
+ font-size: 1.5rem;
+ color: white;
+ background-color: #808080;
+}
+
+div.index_pagination ul.pagination li a:hover {
+ border: 1px solid black;
+}
+
+div.index_pagination ul.pagination li.active a {
+ color: black;
+ border: 1px solid #808080;
+} */
+
+div.index_bottom {
+ padding-top: 15px;
+ text-align: center;
+ word-wrap: break-word;
+ /* 긴 단어 강제 줄바꿈 */
+ white-space: normal;
+ /* 자동 줄바꿈 */
+ /* border: 1px solid red; */
+}
+
+div.index_bottom div.index_action_form {
+ margin-top: 20px;
+ /* border: 1px solid red; */
+}
+
+div.index_bottom div.index_action_form iframe {
+ width: 100%;
+ border: none;
+ /* border: 1px solid blue; */
+}
\ No newline at end of file
diff --git a/public/css/admin/left_menu.css b/public/css/admin/left_menu.css
new file mode 100644
index 0000000..80ca2aa
--- /dev/null
+++ b/public/css/admin/left_menu.css
@@ -0,0 +1,56 @@
+div#left_menu {
+ position: fixed;
+ margin-top: 70px;
+ z-index: 100;
+ border: 1px solid silver;
+}
+
+div#left_menu div#menu_button {
+ position: absolute;
+ top: -1px;
+ right: -20px;
+ width: 20px;
+ height: 160px;
+ cursor: pointer;
+ writing-mode: vertical-rl;
+ /* 세로로 글자를 출력 */
+ text-orientation: upright;
+ /* 글자가 직립되도록 설정 */
+ border-top: 1px solid silver;
+ border-right: 1px solid silver;
+ border-bottom: 1px solid silver;
+ border-radius: 0px 5px 5px 0px;
+ background-color: #e8e9ea;
+}
+
+div#left_menu div.accordion {
+ display: none;
+ width: 20px;
+}
+
+div#left_menu div.accordion div.main {
+ height: 50px;
+ padding-top: 15px;
+ padding-left: 10px;
+ background-color: white;
+ border-bottom: 1px solid silver;
+}
+
+div#left_menu div.accordion div.accordion-item {
+ height: 50px;
+ padding-top: 15px;
+ background-color: #eeeeee;
+ border-bottom: 1px solid silver;
+}
+
+div#left_menu div.accordion div.accordion-item:hover {
+ background-color: silver;
+}
+
+div#left_menu div.accordion div.accordion-item a {
+ padding-left: 20px;
+}
+
+div#left_menu div.accordion div.accordion-collapse a {
+ padding-left: 30px;
+}
\ No newline at end of file
diff --git a/public/css/admin/member_link.css b/public/css/admin/member_link.css
new file mode 100644
index 0000000..38ed839
--- /dev/null
+++ b/public/css/admin/member_link.css
@@ -0,0 +1,17 @@
+nav.top_menu ul.member-link{
+ /* border:1px solid red; */
+ color:#3a37f3;
+ padding-right:20px;
+}
+
+nav.top_menu ul.member-link a{
+ color:#3a37f3;
+}
+
+nav.top_menu ul.member-link ul.dropdown-menu li:hover{
+ background-color: #eaeaea;
+}
+
+nav.top_menu ul.member-link ul.dropdown-menu li a{
+ padding-left:10px;
+}
\ No newline at end of file
diff --git a/public/css/admin/resizeTable.css b/public/css/admin/resizeTable.css
new file mode 100644
index 0000000..8c4e075
--- /dev/null
+++ b/public/css/admin/resizeTable.css
@@ -0,0 +1,67 @@
+.rtc-wrapper table.rtc-table {
+ border-collapse: collapse;
+ white-space: nowrap;
+ margin: 0;
+}
+
+.rtc-wrapper table.rtc-table thead,
+.rtc-wrapper table.rtc-table tbody,
+.rtc-wrapper table.rtc-table tfoot {
+ margin: 0;
+}
+
+.rtc-wrapper table.rtc-table thead tr,
+.rtc-wrapper table.rtc-table tbody tr,
+.rtc-wrapper table.rtc-table tfoot tr {
+ margin: 0;
+}
+
+.rtc-wrapper table.rtc-table thead tr th,
+.rtc-wrapper table.rtc-table thead tr td,
+.rtc-wrapper table.rtc-table tbody tr th,
+.rtc-wrapper table.rtc-table tbody tr td,
+.rtc-wrapper table.rtc-table tfoot tr th,
+.rtc-wrapper table.rtc-table tfoot tr td {
+ margin: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.rtc-wrapper table.rtc-table.rtc-table-resizing {
+ cursor: col-resize;
+}
+
+.rtc-wrapper table.rtc-table.rtc-table-resizing thead,
+.rtc-wrapper table.rtc-table.rtc-table-resizing thead > th,
+.rtc-wrapper table.rtc-table.rtc-table-resizing thead > th > a {
+ cursor: col-resize;
+}
+
+.rtc-wrapper table.rtc-table thead tr.invisible,
+.rtc-wrapper table.rtc-table thead tr.invisible th {
+ border: none;
+ margin: 0;
+ padding: 0;
+ height: 0 !important;
+}
+
+.rtc-wrapper .rtc-handle-container {
+ position: relative;
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+
+.rtc-wrapper .rtc-handle-container .rtc-handle {
+ position: absolute;
+ width: 6.5px;
+ margin-left: -3.575px;
+ z-index: 2;
+ cursor: col-resize;
+}
+
+.rtc-wrapper .rtc-handle-container .rtc-handle:last-of-type {
+ width: 4.5px;
+ margin-left: -4.95px;
+}
\ No newline at end of file
diff --git a/public/css/common/style.css b/public/css/common/style.css
new file mode 100644
index 0000000..54b28ae
--- /dev/null
+++ b/public/css/common/style.css
@@ -0,0 +1,55 @@
+/* ------------------------------------------------------------
+ * Name : admin.css
+ * Desc : Admin StyleSheet
+ * Created : 2016/9/11 Tri-aBility by Junheum,Choi
+ * Updated :
+ ------------------------------------------------------------ */
+@charset "utf-8";
+
+/* user class */
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+strong,
+th,
+.bold {
+ font-weight: 500;
+}
+
+input[type=text],
+input[type=password] {
+ display: inline-block;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ white-space: nowrap;
+}
+
+select,
+textarea,
+button {
+ display: inline-block;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ white-space: nowrap;
+}
+
+a:link {
+ text-decoration: none;
+}
+
+a:visited {
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+a:active {
+ text-decoration: underline;
+}
\ No newline at end of file
diff --git a/public/css/empty.css b/public/css/empty.css
new file mode 100644
index 0000000..1e4df2e
--- /dev/null
+++ b/public/css/empty.css
@@ -0,0 +1,11 @@
+/* ------------------------------------------------------------
+ * Name : admin.css
+ * Desc : Admin StyleSheet
+ * Created : 2016/9/11 Tri-aBility by Junheum,Choi
+ * Updated :
+ ------------------------------------------------------------ */
+* {
+ margin: 0px;
+ padding: 0px;
+ border: 0px;
+}
\ No newline at end of file
diff --git a/public/css/front.css b/public/css/front.css
new file mode 100644
index 0000000..e2255e4
--- /dev/null
+++ b/public/css/front.css
@@ -0,0 +1,81 @@
+/* ------------------------------------------------------------
+ * Name : front.css
+ * Desc : Admin StyleSheet
+ * Created : 2016/9/11 Tri-aBility by Junheum,Choi
+ * Updated :
+ ------------------------------------------------------------ */
+* {
+ margin: 0px;
+ padding: 0px;
+ border: 0px;
+}
+
+html,
+body {
+ height: 100%;
+}
+
+div.layout_top {
+ height: 51px;
+ margin-bottom: 10px;
+ border-top: 1px solid gray;
+ border-bottom: 1px solid gray;
+ background-color: #e8e9ea;
+}
+
+div.layout_bottom {
+ height: 51px;
+ margin-top: 10px;
+ border-top: 1px solid gray;
+ border-bottom: 1px solid gray;
+ background-color: #efefef;
+ background-color: #e8e9ea;
+}
+
+table.layout_middle {
+ width: 100%;
+ /* border: 1px solid blue; */
+}
+
+table.layout_middle td.layout_left {
+ vertical-align: top;
+ /* border: 1px solid red; */
+}
+
+table.layout_middle td.layout_right {
+ padding-top: 5px;
+ padding-left: 23px;
+ padding-right: 5px;
+ padding-bottom: 5px;
+ /* overflow: auto; */
+ /* border: 1px solid blue; */
+}
+
+table.layout_middle td.layout_right div.layout_header {
+ /*content 헤더라인*/
+ height: 55px;
+ padding-top: 15px;
+ background-color: #e7e7e7;
+ border-top: 1px solid gray;
+ border-left: 1px solid gray;
+ border-right: 1px solid gray;
+ border-radius: 15px 15px 0px 0px;
+}
+
+table.layout_middle td.layout_right div.layout_header li.nav-item {}
+
+table.layout_middle td.layout_right div.layout_footer {
+ /*content 하단라인*/
+ height: 20px;
+ border-left: 1px solid gray;
+ border-right: 1px solid gray;
+ border-bottom: 1px solid gray;
+ border-radius: 0px 0px 15px 15px;
+}
+
+table.layout_middle td.layout_right div.layout_content {
+ /*content 부분*/
+ padding: 5px;
+ border-left: 1px solid gray;
+ border-right: 1px solid gray;
+}
\ No newline at end of file
diff --git a/public/css/front/copyright.css b/public/css/front/copyright.css
new file mode 100644
index 0000000..45a4a77
--- /dev/null
+++ b/public/css/front/copyright.css
@@ -0,0 +1,29 @@
+div#copyright{
+ width:100%;
+ height:300px;
+ padding-top:30px;
+ padding-bottom:30px;
+ background-color:#2d2e2e;
+ color:white;
+}
+
+div#copyright div#content_bottom{
+ color:white;
+ text-align:left;
+ /* border-left:1px solid silver;
+ border-right:1px solid silver; */
+}
+
+div#copyright div#content_bottom .item{
+ padding-top:5px;
+ padding-left:5px;
+ padding-right:5px;
+ border-top:1px solid silver;
+ border-left:1px solid silver;
+ border-right:1px solid silver;
+}
+
+div#copyright div#content_bottom div.company_info{
+ padding:10px;
+ border:1px solid silver;
+}
diff --git a/public/css/front/form.css b/public/css/front/form.css
new file mode 100644
index 0000000..7440ad5
--- /dev/null
+++ b/public/css/front/form.css
@@ -0,0 +1,28 @@
+/* create,modify,view 페이지용 */
+div.action_form {
+ /* 블록 요소로 변경 */
+ margin-left: auto;
+ /* 자동 여백을 이용한 가로 가운데 정렬 */
+ margin-right: auto;
+ /* 자동 여백을 이용한 가로 가운데 정렬 */
+ /* border: 1px solid blue; */
+ /* table-layout: fixed; 고정 레이아웃 */
+ border-collapse: collapse;
+ /* 테두리 결합 */
+}
+
+div.action_form table {}
+
+div.action_form table th {
+ background-color: #f0f0f0;
+}
+
+div.action_form table td {
+ text-align: center;
+ word-wrap: break-word;
+ /* 긴 단어 강제 줄바꿈 */
+ white-space: normal;
+ /* 자동 줄바꿈 */
+}
+
+/* create,modify,view 페이지용 */
\ No newline at end of file
diff --git a/public/css/front/index.css b/public/css/front/index.css
new file mode 100644
index 0000000..8a66b59
--- /dev/null
+++ b/public/css/front/index.css
@@ -0,0 +1,168 @@
+/* create,modify,view 페이지용 */
+table.action_form {
+ /* 블록 요소로 변경 */
+ margin-left: auto;
+ /* 자동 여백을 이용한 가로 가운데 정렬 */
+ margin-right: auto;
+ /* 자동 여백을 이용한 가로 가운데 정렬 */
+ /* border: 1px solid blue; */
+ /* table-layout: fixed; 고정 레이아웃 */
+ border-collapse: collapse;
+ /* 테두리 결합 */
+}
+
+table.action_form th {
+ text-align: center;
+ background-color: #f5f5f5;
+}
+
+table.action_form td {
+ text-align: center;
+ word-wrap: break-word;
+ /* 긴 단어 강제 줄바꿈 */
+ white-space: normal;
+ /* 자동 줄바꿈 */
+}
+
+/* create,modify,view 페이지용 */
+
+/*조건검색*/
+nav.index_top nav.condition {
+ border-color: 1px solid red;
+}
+
+/*검색*/
+nav.index_top nav.search {
+ position: relative;
+ height: 30px;
+ border-color: 1px solid red;
+}
+
+nav.index_top nav.search input[type="text"] {
+ width: 200px;
+ height: 30px;
+}
+
+/*검색submit*/
+nav.index_top nav.search input[type="submit"] {
+ font-weight: bold;
+ width: 80px;
+ height: 30px;
+ color: white;
+ background-color: #555555;
+}
+
+/*검색submit*/
+nav.index_top nav.search a.excel {
+ position: absolute;
+ top: -9px;
+ right: -45px;
+ /* border-color: 1px solid red; */
+}
+
+/*페이지정보*/
+nav.index_top nav.pageinfo {
+ font-weight: bold;
+ /* border-color: 1px solid red; */
+}
+
+/* Table 부분 */
+table.index_table {
+ width: 100%;
+ /* table-layout: fixed; */
+ border-collapse: collapse;
+}
+
+table.index_table thead th {
+ white-space: nowrap;
+ padding-top: 15px;
+ padding-bottom: 15px;
+ font-weight: bold;
+ border-top: 2px solid black;
+ border-bottom: 1px solid silver;
+ background-color: #f5f5f5;
+ text-align: center;
+ /* border:1px solid silver; */
+}
+
+table.index_table thead th.index_head_short_column {
+ width: 80px;
+}
+
+table.index_table thead th:active {
+ cursor: grabbing;
+}
+
+table.index_table tbody th {
+ text-align: center;
+ /* border:1px solid silver; */
+}
+
+div.index_batchjob {
+ padding-top: 15px;
+ text-align: center;
+ /* border: 1px solid red; */
+}
+
+div.index_batchjob ul.nav li.nav-item {
+ margin-left: 10px;
+ /* border: 1px solid red; */
+}
+
+div.index_pagination {
+ margin-top: 20px;
+ /* border: 1px solid red; */
+}
+
+div.index_pagination ul.pagination {
+ /* border: 1px solid green; */
+ width: fit-content;
+ /* UL의 너비를 내용에 맞춤 */
+ margin: 0 auto;
+ /* 좌우 마진을 자동으로 설정하여 중앙 배치 */
+ padding: 0;
+ list-style: none;
+ /* 기본 점 스타일 제거 (옵션) */
+}
+
+/* pager의 template가 default_full일경우 사용 */
+/* div.index_pagination ul.pagination li {
+ margin-left: 5px;
+}
+
+div.index_pagination ul.pagination li a {
+ padding: 5px 10px 5px 10px;
+ font-size: 1.5rem;
+ color: white;
+ background-color: #808080;
+}
+
+div.index_pagination ul.pagination li a:hover {
+ border: 1px solid black;
+}
+
+div.index_pagination ul.pagination li.active a {
+ color: black;
+ border: 1px solid #808080;
+} */
+
+div.index_bottom {
+ padding-top: 15px;
+ text-align: center;
+ word-wrap: break-word;
+ /* 긴 단어 강제 줄바꿈 */
+ white-space: normal;
+ /* 자동 줄바꿈 */
+ /* border: 1px solid red; */
+}
+
+div.index_bottom div.index_action_form {
+ margin-top: 20px;
+ /* border: 1px solid red; */
+}
+
+div.index_bottom div.index_action_form iframe {
+ width: 100%;
+ border: none;
+ /* border: 1px solid blue; */
+}
\ No newline at end of file
diff --git a/public/css/front/left_menu.css b/public/css/front/left_menu.css
new file mode 100644
index 0000000..cc89e40
--- /dev/null
+++ b/public/css/front/left_menu.css
@@ -0,0 +1,56 @@
+div#left_menu {
+ position: fixed;
+ margin-top: 60px;
+ z-index: 100;
+ border: 1px solid silver;
+}
+
+div#left_menu div#menu_button {
+ position: absolute;
+ top: -1px;
+ right: -21px;
+ width: 20px;
+ height: 160px;
+ cursor: pointer;
+ writing-mode: vertical-rl;
+ /* 세로로 글자를 출력 */
+ text-orientation: upright;
+ /* 글자가 직립되도록 설정 */
+ border-top: 1px solid silver;
+ border-right: 1px solid silver;
+ border-bottom: 1px solid silver;
+ border-radius: 0px 5px 5px 0px;
+ background-color: #e8e9ea;
+}
+
+div#left_menu div.accordion {
+ display: none;
+ width: 20px;
+}
+
+div#left_menu div.accordion div.main {
+ height: 50px;
+ padding-top: 15px;
+ padding-left: 10px;
+ background-color: white;
+ border-bottom: 1px solid silver;
+}
+
+div#left_menu div.accordion div.accordion-item {
+ height: 50px;
+ padding-top: 15px;
+ background-color: #eeeeee;
+ border-bottom: 1px solid silver;
+}
+
+div#left_menu div.accordion div.accordion-item:hover {
+ background-color: silver;
+}
+
+div#left_menu div.accordion div.accordion-item a {
+ padding-left: 20px;
+}
+
+div#left_menu div.accordion div.accordion-collapse a {
+ padding-left: 30px;
+}
\ No newline at end of file
diff --git a/public/css/front/login.css b/public/css/front/login.css
new file mode 100644
index 0000000..3f80970
--- /dev/null
+++ b/public/css/front/login.css
@@ -0,0 +1,80 @@
+.login-page {
+ background-image: url('/images/login-background.jpg');
+ background-size: cover;
+ background-position: center;
+ height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.login-content {
+ background-color: rgba(255, 255, 255, 0.9);
+ border-radius: 10px;
+ padding: 40px;
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
+ width: 400px;
+}
+
+.login-title {
+ text-align: center;
+ margin-bottom: 30px;
+ color: #333;
+}
+
+.input-group {
+ margin-bottom: 20px;
+}
+
+.input-group label {
+ display: block;
+ margin-bottom: 5px;
+ color: #555;
+}
+
+.input-group input {
+ width: 100%;
+ padding: 10px;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+}
+
+.btn-login {
+ width: 100%;
+ padding: 12px;
+ background-color: #007bff;
+ color: white;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 16px;
+ margin-bottom: 15px;
+}
+
+.login-options {
+ display: flex;
+ justify-content: space-between;
+}
+
+.btn-google,
+.btn-facebook {
+ flex: 1;
+ padding: 10px;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 14px;
+ text-align: center;
+ text-decoration: none;
+}
+
+.btn-google {
+ background-color: #db4437;
+ color: white;
+ margin-right: 10px;
+}
+
+.btn-facebook {
+ background-color: #28a745;
+ color: white;
+}
\ No newline at end of file
diff --git a/public/css/front/member_link.css b/public/css/front/member_link.css
new file mode 100644
index 0000000..38ed839
--- /dev/null
+++ b/public/css/front/member_link.css
@@ -0,0 +1,17 @@
+nav.top_menu ul.member-link{
+ /* border:1px solid red; */
+ color:#3a37f3;
+ padding-right:20px;
+}
+
+nav.top_menu ul.member-link a{
+ color:#3a37f3;
+}
+
+nav.top_menu ul.member-link ul.dropdown-menu li:hover{
+ background-color: #eaeaea;
+}
+
+nav.top_menu ul.member-link ul.dropdown-menu li a{
+ padding-left:10px;
+}
\ No newline at end of file
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..7ecfce2
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/images/common/com_icon3.png b/public/images/common/com_icon3.png
new file mode 100644
index 0000000..eb662cd
Binary files /dev/null and b/public/images/common/com_icon3.png differ
diff --git a/public/images/common/discord.png b/public/images/common/discord.png
new file mode 100644
index 0000000..4a2233c
Binary files /dev/null and b/public/images/common/discord.png differ
diff --git a/public/images/common/excel.png b/public/images/common/excel.png
new file mode 100644
index 0000000..e67fc00
Binary files /dev/null and b/public/images/common/excel.png differ
diff --git a/public/images/common/kakaotalk.png b/public/images/common/kakaotalk.png
new file mode 100644
index 0000000..cf7a87c
Binary files /dev/null and b/public/images/common/kakaotalk.png differ
diff --git a/public/images/common/news.png b/public/images/common/news.png
new file mode 100644
index 0000000..fa77ce2
Binary files /dev/null and b/public/images/common/news.png differ
diff --git a/public/images/common/pdf.png b/public/images/common/pdf.png
new file mode 100644
index 0000000..bb18a19
Binary files /dev/null and b/public/images/common/pdf.png differ
diff --git a/public/images/common/telegram.png b/public/images/common/telegram.png
new file mode 100644
index 0000000..c88f3fa
Binary files /dev/null and b/public/images/common/telegram.png differ
diff --git a/public/images/common/top.png b/public/images/common/top.png
new file mode 100644
index 0000000..84d194d
Binary files /dev/null and b/public/images/common/top.png differ
diff --git a/public/images/common/top_skype.png b/public/images/common/top_skype.png
new file mode 100644
index 0000000..90355c0
Binary files /dev/null and b/public/images/common/top_skype.png differ
diff --git a/public/index.php b/public/index.php
new file mode 100644
index 0000000..5ec58a7
--- /dev/null
+++ b/public/index.php
@@ -0,0 +1,56 @@
+systemDirectory . '/Boot.php';
+
+exit(CodeIgniter\Boot::bootWeb($paths));
diff --git a/public/js/admin.js b/public/js/admin.js
new file mode 100644
index 0000000..a485a38
--- /dev/null
+++ b/public/js/admin.js
@@ -0,0 +1,73 @@
+/* ------------------------------------------------------------
+ * Name : admin.js
+ * Desc : Admin Javascrip
+ * Created : 2016/9/11 Tri-aBility by Junheum,Choi
+ * Updated :
+ ------------------------------------------------------------ */
+
+function is_NumericKey(evt,obj){
+ var charCode = (evt.which) ? evt.which : event.keyCode;
+ switch(charCode){
+ case 48://0
+ case 49://1
+ case 50://2
+ case 51://3
+ case 52://4
+ case 53://5
+ case 54://6
+ case 55://7
+ case 56://8
+ case 57://9
+ case 96://KeyPad:0
+ case 97://KeyPad:1
+ case 98://KeyPad:2
+ case 99://KeyPad:3
+ case 100://KeyPad:4
+ case 101://KeyPad:5
+ case 102://KeyPad:6
+ case 103://KeyPad:7
+ case 104://KeyPad:8
+ case 105://KeyPad:9
+ break;
+ default:
+ alert('숫자만 가능합니다['+charCode+']');
+ obj.value = obj.value.substring(0,obj.value.length-1);
+ break;
+ }
+}
+function is_NumericType(data){
+ if(!data.match(/^[0-9]+$/)){
+ throw (new Error('숫자가 아닌값['+data+']이 있습니다'));
+ }
+ return true;
+}//
+function change_CurrencyFormat(obj,currencies){
+ //var currencies = document.getElementsByClassName("currency");
+ var total_currency = 0;
+ for(i=0; i { alert("복사가 완료되었습니다."); })
+ .catch(err => { console.log('복사가 오류', err); })
+}
\ No newline at end of file
diff --git a/public/js/admin/form.js b/public/js/admin/form.js
new file mode 100644
index 0000000..0c13ad3
--- /dev/null
+++ b/public/js/admin/form.js
@@ -0,0 +1,119 @@
+(function() {
+ //console.log('form.js가 로드되었습니다.');
+
+ function initializeModalComponents(modal) {
+ //console.log('모달 컴포넌트 초기화 시작');
+
+ // 약간의 지연을 주어 모달이 완전히 렌더링되도록 함
+ setTimeout(() => {
+ initializeCalendar(modal);
+ initializeSelectField(modal);
+ initializeTinyMCE(modal);
+ }, 100);
+ }
+
+ function initializeCalendar(container) {
+ const calendarInputs = container.querySelectorAll('.calender');
+ if (calendarInputs.length > 0) {
+ //console.log('달력 초기화 시작');
+ $(calendarInputs).datepicker({
+ changeYear: true,
+ changeMonth: true,
+ yearRange: "-10:+0",
+ dateFormat: "yy-mm-dd"
+ });
+ //console.log('달력 초기화 완료');
+ }
+ }
+
+ function initializeSelectField(container) {
+ const selectFields = container.querySelectorAll('.select-field');
+ if (selectFields.length > 0 && typeof $.fn.select2 !== 'undefined') {
+ //console.log('선택 필드 초기화 시작');
+ $(selectFields).select2({
+ theme: "classic",
+ width: 'style',
+ dropdownAutoWidth: true,
+ dropdownParent: $('#index_action_form'),
+ containerCssClass: 'text-start', // 왼쪽 정렬을 위한 클래스 추가
+ dropdownCssClass: 'text-start' // 드롭다운 메뉴도 왼쪽 정렬
+ });
+ //console.log('선택 필드 초기화 완료');
+ }
+ }
+
+ function initializeTinyMCE(container) {
+ const textareas = container.querySelectorAll('textarea.tinymce');
+ if (textareas.length > 0 && typeof tinymce !== 'undefined') {
+ //console.log('TinyMCE 초기화 시작');
+ tinymce.init({
+ selector: textareas,
+ plugins: ['code', 'image', 'preview', 'table', 'emoticons', 'autoresize'],
+ height: 600,
+ automatic_uploads: false,
+ images_upload_url: '/tinymce_upload.php',
+ images_upload_handler: function (blobInfo, success, failure) {
+ var xhr, formData;
+ xhr = new XMLHttpRequest();
+ xhr.withCredentials = false;
+ xhr.open('POST', '/tinymce_upload.php');
+ xhr.onload = function () {
+ var json;
+ if (xhr.status != 200) {
+ failure('HTTP Error: ' + xhr.status);
+ return;
+ }
+ json = JSON.parse(xhr.responseText);
+ if (!json || typeof json.file_path != 'string') {
+ failure('Invalid JSON: ' + xhr.responseText);
+ return;
+ }
+ success(json.file_path);
+ };
+ formData = new FormData();
+ formData.append('file', blobInfo.blob(), blobInfo.filename());
+ xhr.send(formData);
+ },
+ setup: function(editor) {
+ editor.on('init', function() {
+ //console.log('TinyMCE 에디터 초기화 완료');
+ });
+ }
+ });
+ }
+ }
+
+ // MutationObserver 설정
+ const observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ if (mutation.type === 'childList') {
+ const addedNodes = mutation.addedNodes;
+ for (let i = 0; i < addedNodes.length; i++) {
+ if (addedNodes[i].nodeType === 1 && addedNodes[i].matches('.modal')) {
+ //console.log('새로운 모달이 추가되었습니다.');
+ initializeModalComponents(addedNodes[i]);
+ }
+ }
+ }
+ });
+ });
+
+ // 전체 문서에 대해 MutationObserver 시작
+ observer.observe(document.body, { childList: true, subtree: true });
+
+ // 모달 표시 이벤트 리스너
+ document.body.addEventListener('shown.bs.modal', function(event) {
+ //console.log('모달이 표시되었습니다.');
+ initializeModalComponents(event.target);
+ });
+
+ // 페이지 로드 시 전체 문서에 대해 초기화 실행
+ //console.log('페이지 로드 시 초기화 시작');
+ initializeModalComponents(document.body);
+
+ // 전역 스코프에 함수 노출
+ window.initializeForm = function() {
+ //console.log('initializeForm 함수가 호출되었습니다.');
+ initializeModalComponents(document.body);
+ };
+})();
\ No newline at end of file
diff --git a/public/js/admin/index.js b/public/js/admin/index.js
new file mode 100644
index 0000000..a41c934
--- /dev/null
+++ b/public/js/admin/index.js
@@ -0,0 +1,33 @@
+document.addEventListener('DOMContentLoaded', function() {
+ //class가 calender인 inputbox용,날짜field용
+ if (document.querySelector(".calender")) {
+ $(".calender").datepicker({
+ changeYear: true,
+ changeMonth: true,
+ yearRange: "-10:+0",
+ dateFormat: "yy-mm-dd"
+ });
+ }
+ if (document.querySelector(".batchjobuids_checkboxs")) {
+ //id가 batchjobuids_checkbox인 버튼을 클릭시 class가 batchjobuids_checkboxs인 checkbox용
+ $('#batchjobuids_checkbox').click(function (event) {
+ if (this.checked) {
+ $('.batchjobuids_checkboxs').each(function () { //loop checkbox
+ $(this).prop('checked', true); //check
+ });
+ } else {
+ $('.batchjobuids_checkboxs').each(function () { //loop checkbox
+ $(this).prop('checked', false); //uncheck
+ });
+ }
+ });
+ }
+ if (document.querySelector(".select-field")) {
+ //class가 select-field인 SelectBox용
+ $(".select-field").select2({
+ theme: "classic",
+ width: 'style',
+ dropdownAutoWidth: true
+ });
+ }
+});
\ No newline at end of file
diff --git a/public/js/admin/left_menu.js b/public/js/admin/left_menu.js
new file mode 100644
index 0000000..1cfc98e
--- /dev/null
+++ b/public/js/admin/left_menu.js
@@ -0,0 +1,13 @@
+function sideMenuToggle(left_menu) {
+ $accordion = $("#accordion")[0];
+ if (accordion.clientWidth == 0){
+ accordion.style.display = "block";
+ $("#accordion").css({ "width": '217px' })
+ $("#menu_button").html("메뉴닫기");
+ }
+ else {
+ accordion.style.display = "none";
+ $("#accordion").css({"width":'20px'})
+ $("#menu_button").html("메뉴열기");
+ }
+}//toggleMenu
\ No newline at end of file
diff --git a/public/js/admin/resizeTable.js b/public/js/admin/resizeTable.js
new file mode 100644
index 0000000..58a8c6b
--- /dev/null
+++ b/public/js/admin/resizeTable.js
@@ -0,0 +1,843 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.validide_resizableTableColumns = {}));
+})(this, (function (exports) {
+ 'use strict';
+
+ var ResizableConstants = /** @class */ (function () {
+ function ResizableConstants() {
+ }
+ ResizableConstants.dataPropertyName = 'validide_rtc_data_object';
+ ResizableConstants.classes = {
+ table: 'rtc-table',
+ wrapper: 'rtc-wrapper',
+ handleContainer: 'rtc-handle-container',
+ handle: 'rtc-handle',
+ tableResizing: 'rtc-table-resizing',
+ columnResizing: 'rtc-column-resizing',
+ };
+ ResizableConstants.attributes = {
+ dataResizable: 'data-rtc-resizable',
+ dataResizableTable: 'data-rtc-resizable-table'
+ };
+ ResizableConstants.data = {
+ resizable: 'rtcResizable',
+ resizableTable: 'rtcResizableTable'
+ };
+ ResizableConstants.events = {
+ pointerDown: ['mousedown', 'touchstart'],
+ pointerMove: ['mousemove', 'touchmove'],
+ pointerUp: ['mouseup', 'touchend'],
+ windowResize: ['resize'],
+ eventResizeStart: 'eventResizeStart.rtc',
+ eventResize: 'eventResize.rtc',
+ eventResizeStop: 'eventResizeStop.rtc'
+ };
+ return ResizableConstants;
+ }());
+
+ var WidthsData = /** @class */ (function () {
+ function WidthsData() {
+ this.column = 0;
+ this.table = 0;
+ }
+ return WidthsData;
+ }());
+ var PointerData = /** @class */ (function () {
+ function PointerData() {
+ this.x = null;
+ this.isDoubleClick = false;
+ }
+ return PointerData;
+ }());
+ var ResizableEventData = /** @class */ (function () {
+ function ResizableEventData(column, dragHandler) {
+ this.pointer = new PointerData();
+ this.originalWidths = new WidthsData();
+ this.newWidths = new WidthsData();
+ this.column = column;
+ this.dragHandler = dragHandler;
+ }
+ return ResizableEventData;
+ }());
+
+ var Utilities = /** @class */ (function () {
+ function Utilities() {
+ }
+ Utilities.kebabCaseToCamelCase = function (str) {
+ return str.replace(Utilities.kebabCaseRegex, function (m) { return m[1].toUpperCase(); });
+ };
+ Utilities.parseStringToType = function (str) {
+ if (str.length == 0 || Utilities.onlyWhiteSpace.test(str))
+ return str;
+ if (Utilities.trueRegex.test(str))
+ return true;
+ if (Utilities.falseRegex.test(str))
+ return false;
+ if (Utilities.notEmptyOrWhiteSpace.test(str)) {
+ var temp = +str;
+ if (!isNaN(temp))
+ return temp;
+ }
+ return str;
+ };
+ Utilities.regexEscapeRegex = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;
+ Utilities.kebabCaseRegex = /(\-\w)/g;
+ Utilities.trueRegex = /^true$/i;
+ Utilities.falseRegex = /^false$/i;
+ Utilities.onlyWhiteSpace = /^\s$/;
+ Utilities.notEmptyOrWhiteSpace = /\S/;
+ return Utilities;
+ }());
+
+ var UtilitiesDOM = /** @class */ (function () {
+ function UtilitiesDOM() {
+ }
+ UtilitiesDOM.getDataAttributesValues = function (el) {
+ if (!el)
+ return null;
+ var returnValue = {};
+ if (el.dataset) {
+ for (var prop in el.dataset) {
+ if (el.dataset.hasOwnProperty(prop)) {
+ returnValue[prop] = Utilities.parseStringToType(el.dataset[prop] || '');
+ }
+ }
+ }
+ else {
+ for (var i = 0; i < el.attributes.length; i++) {
+ if (!/^data\-/.test(el.attributes[i].name))
+ continue;
+ var name_1 = Utilities.kebabCaseToCamelCase(el.attributes[i].name.replace('data-', ''));
+ returnValue[name_1] = Utilities.parseStringToType(el.attributes[i].value);
+ }
+ }
+ return returnValue;
+ };
+ return UtilitiesDOM;
+ }());
+
+ var ResizableOptions = /** @class */ (function () {
+ function ResizableOptions(options, element) {
+ if (options === void 0) { options = null; }
+ if (element === void 0) { element = null; }
+ this.resizeFromBody = true;
+ this.minWidth = 40;
+ this.maxWidth = null;
+ this.doubleClickDelay = 500;
+ this.maxInitialWidthHint = null;
+ this.store = null;
+ this.overrideValues(options);
+ this.overrideValuesFromElement(element);
+ }
+ ResizableOptions.prototype.overrideValues = function (options) {
+ if (options === void 0) { options = null; }
+ if (!options)
+ return;
+ for (var prop in options) {
+ if (this.hasOwnProperty(prop)) {
+ this[prop] = options[prop];
+ }
+ }
+ };
+ ResizableOptions.prototype.overrideValuesFromElement = function (element) {
+ if (element === void 0) { element = null; }
+ if (!element)
+ return;
+ var elementOptions = UtilitiesDOM.getDataAttributesValues(element);
+ this.overrideValues(elementOptions);
+ };
+ return ResizableOptions;
+ }());
+
+ var ResizableTableColumns = /** @class */ (function () {
+ function ResizableTableColumns(table, options) {
+ if (typeof table !== 'object' || table === null || table.toString() !== '[object HTMLTableElement]')
+ throw 'Invalid argument: "table".\nResizableTableColumns requires that the table element is a not null HTMLTableElement object!';
+ if (typeof table[ResizableConstants.dataPropertyName] !== 'undefined')
+ throw "Existing \"".concat(ResizableConstants.dataPropertyName, "\" property.\nTable element already has a '").concat(ResizableConstants.dataPropertyName, "' attached object!");
+ this.id = ResizableTableColumns.getInstanceId();
+ this.table = table;
+ this.options = new ResizableOptions(options, table);
+ this.wrapper = null;
+ this.ownerDocument = table.ownerDocument;
+ this.tableHeaders = [];
+ this.dragHandlesContainer = null;
+ this.originalWidths = [];
+ this.eventData = null;
+ this.lastPointerDown = 0;
+ this.init();
+ this.table[ResizableConstants.dataPropertyName] = this;
+ }
+ ResizableTableColumns.prototype.init = function () {
+ this.validateMarkup();
+ this.createHandlerReferences();
+ this.wrapTable();
+ this.assignTableHeaders();
+ this.storeOriginalWidths();
+ this.setHeaderWidths();
+ this.createDragHandles();
+ this.restoreColumnWidths();
+ this.checkTableWidth();
+ this.syncHandleWidths();
+ this.registerWindowResizeHandler();
+ };
+ ResizableTableColumns.prototype.dispose = function () {
+ this.destroyDragHandles();
+ this.restoreOriginalWidths();
+ this.unwrapTable();
+ this.onPointerDownRef = null;
+ this.onPointerMoveRef = null;
+ this.onPointerUpRef = null;
+ this.table[ResizableConstants.dataPropertyName] = void (0);
+ };
+ ResizableTableColumns.prototype.validateMarkup = function () {
+ var theadCount = 0;
+ var tbodyCount = 0;
+ var thead = null;
+ for (var index = 0; index < this.table.childNodes.length; index++) {
+ var element = this.table.childNodes[index];
+ if (element.nodeName === 'THEAD') {
+ theadCount++;
+ thead = element;
+ }
+ else if (element.nodeName === 'TBODY') {
+ tbodyCount++;
+ }
+ }
+ if (thead === null || theadCount !== 1)
+ throw "Markup validation: thead count.\nResizableTableColumns requires that the table element has one(1) table head element. Current count: ".concat(theadCount);
+ if (tbodyCount !== 1)
+ throw "Markup validation: tbody count.\nResizableTableColumns requires that the table element has one(1) table body element. Current count: ".concat(tbodyCount);
+ var theadRowCount = 0;
+ var firstRow = null;
+ for (var index = 0; index < thead.childNodes.length; index++) {
+ var element = thead.childNodes[index];
+ if (element.nodeName === 'TR') {
+ theadRowCount++;
+ if (firstRow === null) {
+ firstRow = element;
+ }
+ }
+ }
+ if (firstRow === null || theadRowCount < 1)
+ throw "Markup validation: thead row count.\nResizableTableColumns requires that the table head element has at least one(1) table row element. Current count: ".concat(theadRowCount);
+ var headerCellsCount = 0;
+ var invalidHeaderCellsCount = 0;
+ for (var index = 0; index < firstRow.childNodes.length; index++) {
+ var element = firstRow.childNodes[index];
+ if (element.nodeName === 'TH') {
+ headerCellsCount++;
+ }
+ else if (element.nodeName === 'TD') {
+ invalidHeaderCellsCount++;
+ }
+ }
+ if (headerCellsCount < 1)
+ throw "Markup validation: thead first row cells count.\nResizableTableColumns requires that the table head's first row element has at least one(1) table header cell element. Current count: ".concat(headerCellsCount);
+ if (invalidHeaderCellsCount !== 0)
+ throw "Markup validation: thead first row invalid.\nResizableTableColumns requires that the table head's first row element has no(0) table cell(TD) elements. Current count: ".concat(invalidHeaderCellsCount);
+ };
+ ResizableTableColumns.prototype.wrapTable = function () {
+ if (this.wrapper)
+ return;
+ this.wrapper = this.ownerDocument.createElement('div');
+ this.wrapper.classList.add(ResizableConstants.classes.wrapper);
+ var tableOriginalParent = this.table.parentNode;
+ tableOriginalParent.insertBefore(this.wrapper, this.table);
+ tableOriginalParent.removeChild(this.table);
+ this.wrapper.appendChild(this.table);
+ this.table.classList.add(ResizableConstants.classes.table);
+ };
+ ResizableTableColumns.prototype.unwrapTable = function () {
+ this.table.classList.remove(ResizableConstants.classes.table);
+ if (!this.wrapper)
+ return;
+ var tableOriginalParent = this.wrapper.parentNode;
+ tableOriginalParent.insertBefore(this.table, this.wrapper);
+ tableOriginalParent.removeChild(this.wrapper);
+ this.wrapper = null;
+ };
+ ResizableTableColumns.prototype.assignTableHeaders = function () {
+ var tableHeader;
+ var firstTableRow;
+ for (var index = 0; index < this.table.childNodes.length; index++) {
+ var element = this.table.childNodes[index];
+ if (element.nodeName === 'THEAD') {
+ tableHeader = element;
+ break;
+ }
+ }
+ if (!tableHeader)
+ return;
+ for (var index = 0; index < tableHeader.childNodes.length; index++) {
+ var element = tableHeader.childNodes[index];
+ if (element.nodeName === 'TR') {
+ firstTableRow = element;
+ break;
+ }
+ }
+ if (!firstTableRow)
+ return;
+ for (var index = 0; index < firstTableRow.childNodes.length; index++) {
+ var element = firstTableRow.childNodes[index];
+ if (element.nodeName === 'TH') {
+ this.tableHeaders.push(element);
+ }
+ }
+ };
+ ResizableTableColumns.prototype.storeOriginalWidths = function () {
+ var _this = this;
+ this.tableHeaders
+ .forEach(function (el) {
+ _this.originalWidths.push({
+ el: el,
+ detail: el.style.width
+ });
+ });
+ this.originalWidths.push({
+ el: this.table,
+ detail: this.table.style.width
+ });
+ };
+ ResizableTableColumns.prototype.restoreOriginalWidths = function () {
+ this.originalWidths
+ .forEach(function (itm) {
+ itm.el.style.width = itm.detail;
+ });
+ };
+ ResizableTableColumns.prototype.setHeaderWidths = function () {
+ var _this = this;
+ this.tableHeaders
+ .forEach(function (el) {
+ var width = el.offsetWidth;
+ var constrainedWidth = _this.constrainWidth(el, width);
+ if (typeof _this.options.maxInitialWidthHint === 'number') {
+ constrainedWidth = Math.min(constrainedWidth, _this.options.maxInitialWidthHint);
+ }
+ _this.updateWidth(el, constrainedWidth, true, false);
+ });
+ };
+ ResizableTableColumns.prototype.constrainWidth = function (el, width) {
+ var result = width;
+ result = Math.max(result, this.options.minWidth || -Infinity);
+ result = Math.min(result, this.options.maxWidth || +Infinity);
+ return result;
+ };
+ ResizableTableColumns.prototype.createDragHandles = function () {
+ var _this = this;
+ var _a;
+ if (this.dragHandlesContainer != null)
+ throw 'Drag handlers already created. Call if you wish to recreate them';
+ this.dragHandlesContainer = this.ownerDocument.createElement('div');
+ (_a = this.wrapper) === null || _a === void 0 ? void 0 : _a.insertBefore(this.dragHandlesContainer, this.table);
+ this.dragHandlesContainer.classList.add(ResizableConstants.classes.handleContainer);
+ this.getResizableHeaders()
+ .forEach(function () {
+ var _a;
+ var handler = _this.ownerDocument.createElement('div');
+ handler.classList.add(ResizableConstants.classes.handle);
+ (_a = _this.dragHandlesContainer) === null || _a === void 0 ? void 0 : _a.appendChild(handler);
+ });
+ ResizableConstants.events.pointerDown
+ .forEach(function (evt, evtIdx) {
+ var _a;
+ (_a = _this.dragHandlesContainer) === null || _a === void 0 ? void 0 : _a.addEventListener(evt, _this.onPointerDownRef, false);
+ });
+ };
+ ResizableTableColumns.prototype.destroyDragHandles = function () {
+ var _this = this;
+ var _a, _b;
+ if (this.dragHandlesContainer !== null) {
+ ResizableConstants.events.pointerDown
+ .forEach(function (evt, evtIdx) {
+ var _a;
+ (_a = _this.dragHandlesContainer) === null || _a === void 0 ? void 0 : _a.removeEventListener(evt, _this.onPointerDownRef, false);
+ });
+ (_b = (_a = this.dragHandlesContainer) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.removeChild(this.dragHandlesContainer);
+ }
+ };
+ ResizableTableColumns.prototype.getDragHandlers = function () {
+ var nodes = this.dragHandlesContainer == null
+ ? null
+ : this.dragHandlesContainer.querySelectorAll(".".concat(ResizableConstants.classes.handle));
+ return nodes
+ ? Array.prototype.slice.call(nodes).filter(function (el) { return el.nodeName === 'DIV'; })
+ : new Array();
+ };
+ ResizableTableColumns.prototype.restoreColumnWidths = function () {
+ if (!this.options.store)
+ return;
+ var tableId = ResizableTableColumns.generateTableId(this.table);
+ if (tableId.length === 0)
+ return;
+ var data = this.options.store.get(tableId);
+ if (!data)
+ return;
+ this.getResizableHeaders()
+ .forEach(function (el) {
+ var width = data.columns[ResizableTableColumns.generateColumnId(el)];
+ if (typeof width !== 'undefined') {
+ ResizableTableColumns.setWidth(el, width);
+ }
+ });
+ if (typeof data.table !== 'undefined') {
+ ResizableTableColumns.setWidth(this.table, data.table);
+ }
+ };
+ ResizableTableColumns.prototype.checkTableWidth = function () {
+ var _a;
+ var wrapperWidth = this.wrapper.clientWidth;
+ var tableWidth = this.table.offsetWidth;
+ var difference = wrapperWidth - tableWidth;
+ if (difference <= 0)
+ return;
+ var resizableWidth = 0;
+ var addedWidth = 0;
+ var headersDetails = [];
+ this.tableHeaders
+ .forEach(function (el, idx) {
+ if (el.hasAttribute(ResizableConstants.attributes.dataResizable)) {
+ var detail = {
+ el: el,
+ detail: el.offsetWidth
+ };
+ headersDetails.push(detail);
+ resizableWidth += detail.detail;
+ }
+ });
+ var leftToAdd = 0;
+ var lastResizableCell = null;
+ var currentDetail;
+ while ((currentDetail = headersDetails.shift())) {
+ leftToAdd = difference - addedWidth;
+ lastResizableCell = currentDetail.el;
+ var extraWidth = Math.floor((currentDetail.detail / resizableWidth) * difference);
+ extraWidth = Math.min(extraWidth, leftToAdd);
+ var newWidth = this.updateWidth(currentDetail.el, currentDetail.detail + extraWidth, false, true);
+ addedWidth += (newWidth - currentDetail.detail);
+ if (addedWidth >= difference)
+ break;
+ }
+ leftToAdd = difference - addedWidth;
+ if (leftToAdd > 0) {
+ var lastCell = ((_a = headersDetails[0]) === null || _a === void 0 ? void 0 : _a.el) || lastResizableCell || this.tableHeaders[this.tableHeaders.length - 1];
+ var lastCellWidth = lastCell.offsetWidth;
+ this.updateWidth(lastCell, lastCellWidth, true, true);
+ }
+ ResizableTableColumns.setWidth(this.table, wrapperWidth);
+ };
+ ResizableTableColumns.prototype.syncHandleWidths = function () {
+ var _this = this;
+ var tableWidth = this.table.clientWidth;
+ ResizableTableColumns.setWidth(this.dragHandlesContainer, tableWidth);
+ this.dragHandlesContainer.style.minWidth = "".concat(tableWidth, "px");
+ var headers = this.getResizableHeaders();
+ this.getDragHandlers()
+ .forEach(function (el, idx) {
+ var height = (_this.options.resizeFromBody ? _this.table : _this.table.tHead).clientHeight;
+ if (idx < headers.length) {
+ var th = headers[idx];
+ var left = th.offsetWidth;
+ left += ResizableTableColumns.getOffset(th).left;
+ left -= ResizableTableColumns.getOffset(_this.dragHandlesContainer).left;
+ el.style.left = "".concat(left, "px");
+ el.style.height = "".concat(height, "px");
+ }
+ });
+ };
+ ResizableTableColumns.prototype.getResizableHeaders = function () {
+ return this.tableHeaders
+ .filter(function (el, idx) {
+ return el.hasAttribute(ResizableConstants.attributes.dataResizable);
+ });
+ };
+ ResizableTableColumns.prototype.handlePointerDown = function (event) {
+ this.handlePointerUp();
+ var target = event ? event.target : null;
+ if (target == null)
+ return;
+ if (target.nodeName !== 'DIV' || !target.classList.contains(ResizableConstants.classes.handle))
+ return;
+ if (typeof event.button === 'number' && event.button !== 0)
+ return; // this is not a left click
+ var dragHandler = target;
+ var gripIndex = this.getDragHandlers().indexOf(dragHandler);
+ var resizableHeaders = this.getResizableHeaders();
+ if (gripIndex >= resizableHeaders.length)
+ return;
+ var millisecondsNow = (new Date()).getTime();
+ var isDoubleClick = (millisecondsNow - this.lastPointerDown) < this.options.doubleClickDelay;
+ var column = resizableHeaders[gripIndex];
+ var columnWidth = column.offsetWidth;
+ var widths = {
+ column: columnWidth,
+ table: this.table.offsetWidth
+ };
+ var eventData = new ResizableEventData(column, dragHandler);
+ eventData.pointer = {
+ x: ResizableTableColumns.getPointerX(event),
+ isDoubleClick: isDoubleClick
+ };
+ eventData.originalWidths = widths;
+ eventData.newWidths = widths;
+ this.detachHandlers(); //make sure we do not have extra handlers
+ this.attachHandlers();
+ this.table.classList.add(ResizableConstants.classes.tableResizing);
+ this.wrapper.classList.add(ResizableConstants.classes.tableResizing);
+ dragHandler.classList.add(ResizableConstants.classes.columnResizing);
+ column.classList.add(ResizableConstants.classes.columnResizing);
+ this.lastPointerDown = millisecondsNow;
+ this.eventData = eventData;
+ var eventToDispatch = new CustomEvent(ResizableConstants.events.eventResizeStart, {
+ detail: {
+ column: column,
+ columnWidth: columnWidth,
+ table: this.table,
+ tableWidth: this.table.clientWidth
+ }
+ });
+ this.table.dispatchEvent(eventToDispatch);
+ event.preventDefault();
+ };
+ ResizableTableColumns.prototype.handlePointerMove = function (event) {
+ if (!this.eventData || !event)
+ return;
+ var difference = (ResizableTableColumns.getPointerX(event) || 0) - (this.eventData.pointer.x || 0);
+ if (difference === 0) {
+ return;
+ }
+ var tableWidth = this.eventData.originalWidths.table + difference;
+ var columnWidth = this.constrainWidth(this.eventData.column, this.eventData.originalWidths.column + difference);
+ ResizableTableColumns.setWidth(this.table, tableWidth);
+ ResizableTableColumns.setWidth(this.eventData.column, columnWidth);
+ this.eventData.newWidths = {
+ column: columnWidth,
+ table: tableWidth
+ };
+ var eventToDispatch = new CustomEvent(ResizableConstants.events.eventResize, {
+ detail: {
+ column: this.eventData.column,
+ columnWidth: columnWidth,
+ table: this.table,
+ tableWidth: tableWidth
+ }
+ });
+ this.table.dispatchEvent(eventToDispatch);
+ };
+ ResizableTableColumns.prototype.handlePointerUp = function () {
+ this.detachHandlers();
+ if (!this.eventData)
+ return;
+ if (this.eventData.pointer.isDoubleClick) {
+ this.handleDoubleClick();
+ }
+ this.table.classList.remove(ResizableConstants.classes.tableResizing);
+ this.wrapper.classList.remove(ResizableConstants.classes.tableResizing);
+ this.eventData.dragHandler.classList.remove(ResizableConstants.classes.columnResizing);
+ this.eventData.column.classList.remove(ResizableConstants.classes.columnResizing);
+ this.checkTableWidth();
+ this.syncHandleWidths();
+ this.refreshWrapperStyle();
+ this.saveColumnWidths();
+ var widths = this.eventData.newWidths || this.eventData.originalWidths;
+ var eventToDispatch = new CustomEvent(ResizableConstants.events.eventResizeStop, {
+ detail: {
+ column: this.eventData.column,
+ columnWidth: widths.column,
+ table: this.table,
+ tableWidth: widths.table
+ }
+ });
+ this.table.dispatchEvent(eventToDispatch);
+ this.eventData = null;
+ };
+ ResizableTableColumns.prototype.handleDoubleClick = function () {
+ if (!this.eventData || !this.eventData.column)
+ return;
+ var column = this.eventData.column;
+ var colIndex = this.tableHeaders.indexOf(column);
+ var maxWidth = 0;
+ var indicesToSkip = [];
+ this.tableHeaders
+ .forEach(function (el, idx) {
+ if (!el.hasAttribute(ResizableConstants.attributes.dataResizable)) {
+ indicesToSkip.push(idx);
+ }
+ });
+ var span = this.ownerDocument.createElement('span');
+ span.style.position = 'absolute';
+ span.style.left = '-99999px';
+ span.style.top = '-99999px';
+ span.style.visibility = 'hidden';
+ this.ownerDocument.body.appendChild(span);
+ var rows = this.table.querySelectorAll('tr');
+ for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) {
+ var element = rows[rowIndex];
+ var cells = element.querySelectorAll('td, th');
+ var currentIndex = 0;
+ for (var cellIndex = 0; cellIndex < cells.length; cellIndex++) {
+ var cell = cells[cellIndex];
+ var colSpan = 1;
+ if (cell.hasAttribute('colspan')) {
+ var colSpanString = cell.getAttribute('colspan') || '1';
+ var parsed = parseInt(colSpanString);
+ if (!isNaN(parsed)) {
+ colSpan = parsed;
+ }
+ else {
+ colSpan = 1;
+ }
+ }
+ if (indicesToSkip.indexOf(cellIndex) === -1
+ && colSpan === 1
+ && currentIndex === colIndex) {
+ maxWidth = Math.max(maxWidth, ResizableTableColumns.getTextWidth(cell, span));
+ break;
+ }
+ currentIndex += colSpan;
+ }
+ }
+ this.ownerDocument.body.removeChild(span);
+ var difference = maxWidth - column.offsetWidth;
+ if (difference === 0) {
+ return;
+ }
+ var tableWidth = this.eventData.originalWidths.table + difference;
+ var columnWidth = this.constrainWidth(this.eventData.column, this.eventData.originalWidths.column + difference);
+ ResizableTableColumns.setWidth(this.table, tableWidth);
+ ResizableTableColumns.setWidth(this.eventData.column, columnWidth);
+ this.eventData.newWidths = {
+ column: columnWidth,
+ table: tableWidth,
+ };
+ var eventToDispatch = new CustomEvent(ResizableConstants.events.eventResize, {
+ detail: {
+ column: this.eventData.column,
+ columnWidth: columnWidth,
+ table: this.table,
+ tableWidth: tableWidth
+ }
+ });
+ this.table.dispatchEvent(eventToDispatch);
+ this.checkTableWidth();
+ this.syncHandleWidths();
+ this.saveColumnWidths();
+ };
+ ResizableTableColumns.prototype.attachHandlers = function () {
+ var _this = this;
+ ResizableConstants.events.pointerMove
+ .forEach(function (evt, evtIdx) {
+ _this.ownerDocument.addEventListener(evt, _this.onPointerMoveRef, false);
+ });
+ ResizableConstants.events.pointerUp
+ .forEach(function (evt, evtIdx) {
+ _this.ownerDocument.addEventListener(evt, _this.onPointerUpRef, false);
+ });
+ };
+ ResizableTableColumns.prototype.detachHandlers = function () {
+ var _this = this;
+ ResizableConstants.events.pointerMove
+ .forEach(function (evt, evtIdx) {
+ _this.ownerDocument.removeEventListener(evt, _this.onPointerMoveRef, false);
+ });
+ ResizableConstants.events.pointerUp
+ .forEach(function (evt, evtIdx) {
+ _this.ownerDocument.removeEventListener(evt, _this.onPointerUpRef, false);
+ });
+ };
+ ResizableTableColumns.prototype.refreshWrapperStyle = function () {
+ if (this.wrapper == null)
+ return;
+ var original = this.wrapper.style.overflowX;
+ this.wrapper.style.overflowX = 'hidden';
+ this.wrapper.style.overflowX = original;
+ };
+ ResizableTableColumns.prototype.saveColumnWidths = function () {
+ if (!this.options.store)
+ return;
+ var tableId = ResizableTableColumns.generateTableId(this.table);
+ if (tableId.length === 0)
+ return;
+ var data = {
+ table: this.table.offsetWidth,
+ columns: {}
+ };
+ this.getResizableHeaders()
+ .forEach(function (el) {
+ data.columns[ResizableTableColumns.generateColumnId(el)] = el.offsetWidth;
+ });
+ this.options.store.set(tableId, data);
+ };
+ ResizableTableColumns.prototype.createHandlerReferences = function () {
+ var _this = this;
+ if (!this.onPointerDownRef) {
+ this.onPointerDownRef = ResizableTableColumns.debounce(function (evt) {
+ _this.handlePointerDown(evt);
+ }, 100, true);
+ }
+ if (!this.onPointerMoveRef) {
+ this.onPointerMoveRef = ResizableTableColumns.debounce(function (evt) {
+ _this.handlePointerMove(evt);
+ }, 5, false);
+ }
+ if (!this.onPointerUpRef) {
+ this.onPointerUpRef = ResizableTableColumns.debounce(function (evt) {
+ _this.handlePointerUp();
+ }, 100, true);
+ }
+ };
+ ResizableTableColumns.prototype.registerWindowResizeHandler = function () {
+ var win = this.ownerDocument.defaultView;
+ if (ResizableTableColumns.windowResizeHandlerRef)
+ return;
+ ResizableTableColumns.windowResizeHandlerRef = ResizableTableColumns.debounce(ResizableTableColumns.onWindowResize, 50, false);
+ ResizableConstants.events.windowResize
+ .forEach(function (evt, idx) {
+ win === null || win === void 0 ? void 0 : win.addEventListener(evt, ResizableTableColumns.windowResizeHandlerRef, false);
+ });
+ };
+ ResizableTableColumns.prototype.handleWindowResize = function () {
+ this.checkTableWidth();
+ this.syncHandleWidths();
+ this.saveColumnWidths();
+ };
+ ResizableTableColumns.prototype.updateWidth = function (cell, suggestedWidth, skipConstrainCheck, skipTableResize) {
+ var originalCellWidth = cell.offsetWidth;
+ var columnWidth = skipConstrainCheck
+ ? suggestedWidth
+ : this.constrainWidth(cell, suggestedWidth);
+ ResizableTableColumns.setWidth(cell, columnWidth);
+ if (!skipTableResize) {
+ var difference = columnWidth - originalCellWidth;
+ var tableWidth = this.table.offsetWidth + difference;
+ ResizableTableColumns.setWidth(this.table, tableWidth);
+ }
+ return columnWidth;
+ };
+ ResizableTableColumns.onWindowResize = function (event) {
+ var win = event ? event.target : null;
+ if (win == null)
+ return;
+ var tables = win.document.querySelectorAll(".".concat(ResizableConstants.classes.table));
+ for (var index = 0; index < tables.length; index++) {
+ var table = tables[index];
+ if (typeof table[ResizableConstants.dataPropertyName] !== 'object')
+ continue;
+ table[ResizableConstants.dataPropertyName].handleWindowResize();
+ }
+ };
+ ResizableTableColumns.generateColumnId = function (el) {
+ var columnId = (el.getAttribute(ResizableConstants.attributes.dataResizable) || '')
+ .trim()
+ .replace(/\./g, '_');
+ return columnId;
+ };
+ ResizableTableColumns.generateTableId = function (table) {
+ var tableId = (table.getAttribute(ResizableConstants.attributes.dataResizableTable) || '')
+ .trim()
+ .replace(/\./g, '_');
+ return tableId.length
+ ? "rtc/".concat(tableId)
+ : tableId;
+ };
+ ResizableTableColumns.setWidth = function (element, width) {
+ var strWidth = width.toFixed(2);
+ strWidth = width > 0 ? strWidth : '0';
+ element.style.width = "".concat(strWidth, "px");
+ };
+ ResizableTableColumns.getInstanceId = function () {
+ return ResizableTableColumns.instancesCount++;
+ };
+ ResizableTableColumns.getPointerX = function (event) {
+ if (event.type.indexOf('touch') === 0) {
+ var tEvent = event;
+ if (tEvent.touches && tEvent.touches.length) {
+ return tEvent.touches[0].pageX;
+ }
+ if (tEvent.changedTouches && tEvent.changedTouches.length) {
+ return tEvent.changedTouches[0].pageX;
+ }
+ }
+ return event.pageX;
+ };
+ ResizableTableColumns.getTextWidth = function (contentElement, measurementElement) {
+ var _a, _b;
+ if (!contentElement || !measurementElement)
+ return 0;
+ var text = ((_a = contentElement.textContent) === null || _a === void 0 ? void 0 : _a.trim().replace(/\s/g, ' ')) + ' '; //add extra space to ensure we are not add the `...`
+ var styles = (_b = contentElement.ownerDocument.defaultView) === null || _b === void 0 ? void 0 : _b.getComputedStyle(contentElement);
+ ['fontFamily', 'fontSize', 'fontWeight', 'padding', 'border', 'boxSizing']
+ .forEach(function (prop) {
+ measurementElement.style[prop] = styles[prop];
+ });
+ measurementElement.innerHTML = text;
+ return measurementElement.offsetWidth;
+ };
+ ResizableTableColumns.getOffset = function (el) {
+ if (!el)
+ return { top: 0, left: 0 };
+ var rect = el.getBoundingClientRect();
+ return {
+ top: rect.top + el.ownerDocument.body.scrollTop,
+ left: rect.left + el.ownerDocument.body.scrollLeft
+ };
+ };
+ ResizableTableColumns.instancesCount = 0;
+ ResizableTableColumns.windowResizeHandlerRef = null;
+ ResizableTableColumns.debounce = function (func, wait, immediate) {
+ var timeout = null;
+ var debounced = function () {
+ var args = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ args[_i] = arguments[_i];
+ }
+ var later = function () {
+ timeout = null;
+ if (!immediate) {
+ func.apply(void 0, args);
+ }
+ };
+ var callNow = immediate && !timeout;
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+ timeout = setTimeout(later, wait);
+ if (callNow) {
+ func.apply(void 0, args);
+ }
+ };
+ return debounced;
+ };
+ return ResizableTableColumns;
+ }());
+
+ exports.PointerData = PointerData;
+ exports.ResizableConstants = ResizableConstants;
+ exports.ResizableEventData = ResizableEventData;
+ exports.ResizableOptions = ResizableOptions;
+ exports.ResizableTableColumns = ResizableTableColumns;
+ exports.Utilities = Utilities;
+ exports.UtilitiesDOM = UtilitiesDOM;
+ exports.WidthsData = WidthsData;
+
+}));
+(function (window, ResizableTableColumns, undefined) {
+ var store = window.store && window.store.enabled ? window.store : null;
+ var els = document.querySelectorAll('table.data');
+ for (var index = 0; index < els.length; index++) {
+ var table = els[index];
+ if (table['rtc_data_object']) {
+ continue;
+ }
+ var options = {
+ store: store
+ };
+ if (table.querySelectorAll('thead > tr').length > 1) {
+ options.resizeFromBody = false;
+ }
+ new ResizableTableColumns(els[index], options);
+ }
+})(window, window.validide_resizableTableColumns.ResizableTableColumns, void (0));
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/public/js/common/product.js b/public/js/common/product.js
new file mode 100644
index 0000000..673a2ed
--- /dev/null
+++ b/public/js/common/product.js
@@ -0,0 +1,30 @@
+
+function calculator(order_price) {
+ var parts = Array.from(document.getElementsByClassName("vhost_parts"));
+ parts.forEach(function(part) { //loop
+ //console.log(part);
+ order_price += parseInt((part.getAttribute('cost') - part.getAttribute('sale')) * part.options[part.selectedIndex].value);
+ document.getElementById('price').value = order_price;
+ document.getElementById('order_price').textContent = new Intl.NumberFormat().format(order_price);
+ });
+ var current = document.getElementById('paymentday');
+ if (!current.selectedIndex) {
+ alert("결제일을 선택해주세요");
+ current.focus();
+ return false
+ }
+}
+function addDevice(category, key, label) {
+ var categoryBox = document.getElementById(category + "Box");
+ var div = document.createElement("div");
+ var checkbox = document.createElement("input");
+ checkbox.setAttribute("type", "checkbox");
+ checkbox.setAttribute("name", category + '[]');
+ checkbox.setAttribute("value", key);
+ checkbox.setAttribute("checked", true);
+ checkbox.setAttribute("class", 'device');
+ div.appendChild(checkbox);
+ div.appendChild(document.createTextNode(label));
+ // console.log(div);
+ categoryBox.appendChild(div);
+}
\ No newline at end of file
diff --git a/public/js/empty.js b/public/js/empty.js
new file mode 100644
index 0000000..f973e71
--- /dev/null
+++ b/public/js/empty.js
@@ -0,0 +1,6 @@
+/* ------------------------------------------------------------
+ * Name : front.js
+ * Desc : Front Javascrip
+ * Created : 2016/9/11 Tri-aBility by Junheum,Choi
+ * Updated :
+ ------------------------------------------------------------ */
diff --git a/public/js/front.js b/public/js/front.js
new file mode 100644
index 0000000..6bea8a4
--- /dev/null
+++ b/public/js/front.js
@@ -0,0 +1,93 @@
+/* ------------------------------------------------------------
+ * Name : front.js
+ * Desc : Front Javascrip
+ * Created : 2016/9/11 Tri-aBility by Junheum,Choi
+ * Updated :
+ ------------------------------------------------------------ */
+
+function trim(str){
+ return this.replace(/(^\s*)|(\s*$)/gi, "");
+}//
+
+function bookmarksite(title,url) {
+ if (window.sidebar) // firefox
+ window.sidebar.addPanel(title, url, "");
+ else if(window.opera && window.print){ // opera
+ var elem = document.createElement('a');
+ elem.setAttribute('href',url);
+ elem.setAttribute('title',title);
+ elem.setAttribute('rel','sidebar');
+ elem.click();
+ }
+ else if(document.all) // ie
+ window.external.AddFavorite(url, title);
+}//
+
+function captcha_refresh(refresh_url) {
+ $.ajax({
+ type: 'POST',
+ url: refresh_url,
+ success: function(data, status, xhr){
+ if(data)
+ $('#captcha_span').html(data);
+ },
+ error: function(jqXHR, textStatus, errorThrown) {
+ console.log(jqXHR.responseText);
+ console.log(textStatus+'=>'+errorThrown);
+ }
+ });//ajax
+}//
+
+function is_NumericKey(evt,obj){
+ var charCode = (evt.which) ? evt.which : event.keyCode;
+ switch(charCode){
+ case 48://0
+ case 49://1
+ case 50://2
+ case 51://3
+ case 52://4
+ case 53://5
+ case 54://6
+ case 55://7
+ case 56://8
+ case 57://9
+ case 96://KeyPad:0
+ case 97://KeyPad:1
+ case 98://KeyPad:2
+ case 99://KeyPad:3
+ case 100://KeyPad:4
+ case 101://KeyPad:5
+ case 102://KeyPad:6
+ case 103://KeyPad:7
+ case 104://KeyPad:8
+ case 105://KeyPad:9
+ break;
+ default:
+ alert('숫자만 가능합니다['+charCode+']');
+ obj.value = obj.value.substring(0,obj.value.length-1);
+ break;
+ }
+}
+function is_NumericType(data){
+ if(!data.match(/^[0-9]+$/)){
+ throw (new Error('숫자가 아닌값['+data+']이 있습니다'));
+ }
+ return true;
+}//
+function change_CurrencyFormat(obj,currencies){
+ //var currencies = document.getElementsByClassName("currency");
+ var total_currency = 0;
+ for(i=0; i $fileName
+ ));
+}
diff --git a/spark b/spark
new file mode 100644
index 0000000..992d044
--- /dev/null
+++ b/spark
@@ -0,0 +1,84 @@
+#!/usr/bin/env php
+
+ *
+ * For the full copyright and license information, please view
+ * the LICENSE file that was distributed with this source code.
+ */
+
+/*
+ * --------------------------------------------------------------------
+ * CODEIGNITER COMMAND-LINE TOOLS
+ * --------------------------------------------------------------------
+ * The main entry point into the CLI system and allows you to run
+ * commands and perform maintenance on your application.
+ */
+
+/*
+ *---------------------------------------------------------------
+ * CHECK SERVER API
+ *---------------------------------------------------------------
+ */
+
+// Refuse to run when called from php-cgi
+if (str_starts_with(PHP_SAPI, 'cgi')) {
+ exit("The cli tool is not supported when running php-cgi. It needs php-cli to function!\n\n");
+}
+
+/*
+ *---------------------------------------------------------------
+ * CHECK PHP VERSION
+ *---------------------------------------------------------------
+ */
+
+$minPhpVersion = '8.1'; // If you update this, don't forget to update `public/index.php`.
+if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
+ $message = sprintf(
+ 'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s',
+ $minPhpVersion,
+ PHP_VERSION
+ );
+
+ exit($message);
+}
+
+// We want errors to be shown when using it from the CLI.
+error_reporting(E_ALL);
+ini_set('display_errors', '1');
+
+/*
+ *---------------------------------------------------------------
+ * SET THE CURRENT DIRECTORY
+ *---------------------------------------------------------------
+ */
+
+// Path to the front controller
+define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR);
+
+// Ensure the current directory is pointing to the front controller's directory
+chdir(FCPATH);
+
+/*
+ *---------------------------------------------------------------
+ * BOOTSTRAP THE APPLICATION
+ *---------------------------------------------------------------
+ * This process sets up the path constants, loads and registers
+ * our autoloader, along with Composer's, loads our constants
+ * and fires up an environment-specific bootstrapping.
+ */
+
+// LOAD OUR PATHS CONFIG FILE
+// This is the line that might need to be changed, depending on your folder structure.
+require FCPATH . '../app/Config/Paths.php';
+// ^^^ Change this line if you move your application folder
+
+$paths = new Config\Paths();
+
+// LOAD THE FRAMEWORK BOOTSTRAP FILE
+require $paths->systemDirectory . '/Boot.php';
+
+exit(CodeIgniter\Boot::bootSpark($paths));
diff --git a/tests/.htaccess b/tests/.htaccess
new file mode 100644
index 0000000..3462048
--- /dev/null
+++ b/tests/.htaccess
@@ -0,0 +1,6 @@
+
+ Require all denied
+
+
+ Deny from all
+
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..fc40e44
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,118 @@
+# Running Application Tests
+
+This is the quick-start to CodeIgniter testing. Its intent is to describe what
+it takes to set up your application and get it ready to run unit tests.
+It is not intended to be a full description of the test features that you can
+use to test your application. Those details can be found in the documentation.
+
+## Resources
+
+* [CodeIgniter 4 User Guide on Testing](https://codeigniter.com/user_guide/testing/index.html)
+* [PHPUnit docs](https://phpunit.de/documentation.html)
+* [Any tutorials on Unit testing in CI4?](https://forum.codeigniter.com/showthread.php?tid=81830)
+
+## Requirements
+
+It is recommended to use the latest version of PHPUnit. At the time of this
+writing, we are running version 9.x. Support for this has been built into the
+**composer.json** file that ships with CodeIgniter and can easily be installed
+via [Composer](https://getcomposer.org/) if you don't already have it installed globally.
+
+```console
+> composer install
+```
+
+If running under macOS or Linux, you can create a symbolic link to make running tests a touch nicer.
+
+```console
+> ln -s ./vendor/bin/phpunit ./phpunit
+```
+
+You also need to install [XDebug](https://xdebug.org/docs/install) in order
+for code coverage to be calculated successfully. After installing `XDebug`, you must add `xdebug.mode=coverage` in the **php.ini** file to enable code coverage.
+
+## Setting Up
+
+A number of the tests use a running database.
+In order to set up the database edit the details for the `tests` group in
+**app/Config/Database.php** or **.env**.
+Make sure that you provide a database engine that is currently running on your machine.
+More details on a test database setup are in the
+[Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) section of the documentation.
+
+## Running the tests
+
+The entire test suite can be run by simply typing one command-line command from the main directory.
+
+```console
+> ./phpunit
+```
+
+If you are using Windows, use the following command.
+
+```console
+> vendor\bin\phpunit
+```
+
+You can limit tests to those within a single test directory by specifying the
+directory name after phpunit.
+
+```console
+> ./phpunit app/Models
+```
+
+## Generating Code Coverage
+
+To generate coverage information, including HTML reports you can view in your browser,
+you can use the following command:
+
+```console
+> ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m
+```
+
+This runs all of the tests again collecting information about how many lines,
+functions, and files are tested. It also reports the percentage of the code that is covered by tests.
+It is collected in two formats: a simple text file that provides an overview as well
+as a comprehensive collection of HTML files that show the status of every line of code in the project.
+
+The text file can be found at **tests/coverage.txt**.
+The HTML files can be viewed by opening **tests/coverage/index.html** in your favorite browser.
+
+## PHPUnit XML Configuration
+
+The repository has a ``phpunit.xml.dist`` file in the project root that's used for
+PHPUnit configuration. This is used to provide a default configuration if you
+do not have your own configuration file in the project root.
+
+The normal practice would be to copy ``phpunit.xml.dist`` to ``phpunit.xml``
+(which is git ignored), and to tailor it as you see fit.
+For instance, you might wish to exclude database tests, or automatically generate
+HTML code coverage reports.
+
+## Test Cases
+
+Every test needs a *test case*, or class that your tests extend. CodeIgniter 4
+provides one class that you may use directly:
+* `CodeIgniter\Test\CIUnitTestCase`
+
+Most of the time you will want to write your own test cases that extend `CIUnitTestCase`
+to hold functions and services common to your test suites.
+
+## Creating Tests
+
+All tests go in the **tests/** directory. Each test file is a class that extends a
+**Test Case** (see above) and contains methods for the individual tests. These method
+names must start with the word "test" and should have descriptive names for precisely what
+they are testing:
+`testUserCanModifyFile()` `testOutputColorMatchesInput()` `testIsLoggedInFailsWithInvalidUser()`
+
+Writing tests is an art, and there are many resources available to help learn how.
+Review the links above and always pay attention to your code coverage.
+
+### Database Tests
+
+Tests can include migrating, seeding, and testing against a mock or live database.
+Be sure to modify the test case (or create your own) to point to your seed and migrations
+and include any additional steps to be run before tests in the `setUp()` method.
+See [Testing Your Database](https://codeigniter.com/user_guide/testing/database.html)
+for details.
diff --git a/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php b/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php
new file mode 100644
index 0000000..a73356d
--- /dev/null
+++ b/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php
@@ -0,0 +1,37 @@
+forge->addField('id');
+ $this->forge->addField([
+ 'name' => ['type' => 'varchar', 'constraint' => 31],
+ 'uid' => ['type' => 'varchar', 'constraint' => 31],
+ 'class' => ['type' => 'varchar', 'constraint' => 63],
+ 'icon' => ['type' => 'varchar', 'constraint' => 31],
+ 'summary' => ['type' => 'varchar', 'constraint' => 255],
+ 'created_at' => ['type' => 'datetime', 'null' => true],
+ 'updated_at' => ['type' => 'datetime', 'null' => true],
+ 'deleted_at' => ['type' => 'datetime', 'null' => true],
+ ]);
+
+ $this->forge->addKey('name');
+ $this->forge->addKey('uid');
+ $this->forge->addKey(['deleted_at', 'id']);
+ $this->forge->addKey('created_at');
+
+ $this->forge->createTable('factories');
+ }
+
+ public function down(): void
+ {
+ $this->forge->dropTable('factories');
+ }
+}
diff --git a/tests/_support/Database/Seeds/ExampleSeeder.php b/tests/_support/Database/Seeds/ExampleSeeder.php
new file mode 100644
index 0000000..619fc27
--- /dev/null
+++ b/tests/_support/Database/Seeds/ExampleSeeder.php
@@ -0,0 +1,41 @@
+ 'Test Factory',
+ 'uid' => 'test001',
+ 'class' => 'Factories\Tests\NewFactory',
+ 'icon' => 'fas fa-puzzle-piece',
+ 'summary' => 'Longer sample text for testing',
+ ],
+ [
+ 'name' => 'Widget Factory',
+ 'uid' => 'widget',
+ 'class' => 'Factories\Tests\WidgetPlant',
+ 'icon' => 'fas fa-puzzle-piece',
+ 'summary' => 'Create widgets in your factory',
+ ],
+ [
+ 'name' => 'Evil Factory',
+ 'uid' => 'evil-maker',
+ 'class' => 'Factories\Evil\MyFactory',
+ 'icon' => 'fas fa-book-dead',
+ 'summary' => 'Abandon all hope, ye who enter here',
+ ],
+ ];
+
+ $builder = $this->db->table('factories');
+
+ foreach ($factories as $factory) {
+ $builder->insert($factory);
+ }
+ }
+}
diff --git a/tests/_support/Libraries/ConfigReader.php b/tests/_support/Libraries/ConfigReader.php
new file mode 100644
index 0000000..40d057a
--- /dev/null
+++ b/tests/_support/Libraries/ConfigReader.php
@@ -0,0 +1,17 @@
+findAll();
+
+ // Make sure the count is as expected
+ $this->assertCount(3, $objects);
+ }
+
+ public function testSoftDeleteLeavesRow(): void
+ {
+ $model = new ExampleModel();
+ $this->setPrivateProperty($model, 'useSoftDeletes', true);
+ $this->setPrivateProperty($model, 'tempUseSoftDeletes', true);
+
+ /** @var stdClass $object */
+ $object = $model->first();
+ $model->delete($object->id);
+
+ // The model should no longer find it
+ $this->assertNull($model->find($object->id));
+
+ // ... but it should still be in the database
+ $result = $model->builder()->where('id', $object->id)->get()->getResult();
+
+ $this->assertCount(1, $result);
+ }
+}
diff --git a/tests/index.html b/tests/index.html
new file mode 100644
index 0000000..b702fbc
--- /dev/null
+++ b/tests/index.html
@@ -0,0 +1,11 @@
+
+
+
+ 403 Forbidden
+
+
+
+Directory access is forbidden.
+
+
+
diff --git a/tests/session/ExampleSessionTest.php b/tests/session/ExampleSessionTest.php
new file mode 100644
index 0000000..6ada0c5
--- /dev/null
+++ b/tests/session/ExampleSessionTest.php
@@ -0,0 +1,18 @@
+set('logged_in', 123);
+ $this->assertSame(123, $session->get('logged_in'));
+ }
+}
diff --git a/tests/unit/HealthTest.php b/tests/unit/HealthTest.php
new file mode 100644
index 0000000..25f229b
--- /dev/null
+++ b/tests/unit/HealthTest.php
@@ -0,0 +1,50 @@
+assertTrue(defined('APPPATH'));
+ }
+
+ public function testBaseUrlHasBeenSet(): void
+ {
+ $validation = Services::validation();
+
+ $env = false;
+
+ // Check the baseURL in .env
+ if (is_file(HOMEPATH . '.env')) {
+ $env = preg_grep('/^app\.baseURL = ./', file(HOMEPATH . '.env')) !== false;
+ }
+
+ if ($env) {
+ // BaseURL in .env is a valid URL?
+ // phpunit.xml.dist sets app.baseURL in $_SERVER
+ // So if you set app.baseURL in .env, it takes precedence
+ $config = new App();
+ $this->assertTrue(
+ $validation->check($config->baseURL, 'valid_url'),
+ 'baseURL "' . $config->baseURL . '" in .env is not valid URL'
+ );
+ }
+
+ // Get the baseURL in app/Config/App.php
+ // You can't use Config\App, because phpunit.xml.dist sets app.baseURL
+ $reader = new ConfigReader();
+
+ // BaseURL in app/Config/App.php is a valid URL?
+ $this->assertTrue(
+ $validation->check($reader->baseURL, 'valid_url'),
+ 'baseURL "' . $reader->baseURL . '" in app/Config/App.php is not valid URL'
+ );
+ }
+}
diff --git a/writable/.htaccess b/writable/.htaccess
new file mode 100644
index 0000000..a94c5b0
--- /dev/null
+++ b/writable/.htaccess
@@ -0,0 +1,12 @@
+
+ Require all denied
+
+
+ Deny from all
+
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^(.*)$ index.php/$1 [L]
+
\ No newline at end of file
diff --git a/writable/cache/index.html b/writable/cache/index.html
new file mode 100644
index 0000000..b702fbc
--- /dev/null
+++ b/writable/cache/index.html
@@ -0,0 +1,11 @@
+
+
+
+ 403 Forbidden
+
+
+
+Directory access is forbidden.
+
+
+
diff --git a/writable/debugbar/index.html b/writable/debugbar/index.html
new file mode 100644
index 0000000..b702fbc
--- /dev/null
+++ b/writable/debugbar/index.html
@@ -0,0 +1,11 @@
+
+
+
+ 403 Forbidden
+
+
+
+Directory access is forbidden.
+
+
+
diff --git a/writable/excel/index.html b/writable/excel/index.html
new file mode 100644
index 0000000..b702fbc
--- /dev/null
+++ b/writable/excel/index.html
@@ -0,0 +1,11 @@
+
+
+
+ 403 Forbidden
+
+
+
+Directory access is forbidden.
+
+
+
diff --git a/writable/index.html b/writable/index.html
new file mode 100644
index 0000000..b702fbc
--- /dev/null
+++ b/writable/index.html
@@ -0,0 +1,11 @@
+
+
+
+ 403 Forbidden
+
+
+
+Directory access is forbidden.
+
+
+
diff --git a/writable/logs/index.html b/writable/logs/index.html
new file mode 100644
index 0000000..b702fbc
--- /dev/null
+++ b/writable/logs/index.html
@@ -0,0 +1,11 @@
+
+
+
+ 403 Forbidden
+
+
+
+Directory access is forbidden.
+
+
+
diff --git a/writable/uploads/index.html b/writable/uploads/index.html
new file mode 100644
index 0000000..b702fbc
--- /dev/null
+++ b/writable/uploads/index.html
@@ -0,0 +1,11 @@
+
+
+
+ 403 Forbidden
+
+
+
+Directory access is forbidden.
+
+
+