servermgrv2 init...

This commit is contained in:
최준흠 2023-07-19 10:29:08 +09:00
parent bbd2c11f28
commit e493e59c85
17 changed files with 173 additions and 162 deletions

View File

@ -1,4 +1,5 @@
#Tips
#참고 : https://github.com/bundanining/Shopping-Cart-Solution-CodeIgniter
vscode와 Git의 대소문자 구분시키기
git config core.ignorecase false

View File

@ -152,24 +152,27 @@ define('AUTHS', [
'TOKEN_NAME' => getenv('auth.google.client.token_name') ? getenv('auth.google.client.token_name') : "access_token",
],
]);
//SITE_Default 정의
define('DEFAULTS', [
'ROLE' => getenv('default.role') ? getenv('default.role') : "user",
'STATUS' => getenv('default.status') ? getenv('default.status') : "use",
'EMPTY' => getenv('default.empty') ? getenv('default.empty') : "",
'PERPAGE' => getenv('default.perpage') ? getenv('default.perpage') : 20,
'EXCEL_PATH' => getenv('default.excel_path') ? getenv('default.excel_path') : "../writable/Excel",
//등급 관련
define('ROLES', [
'user' => '일반회원', 'vip' => 'VIP회원',
'bronze' => '일반판매자', 'silver' => '고급판매자', 'gold' => '파워리셀러', 'mallmaster' => "쇼핑몰관리자",
'manager' => '관리자', 'cloudflare' => "Cloudflare관리자", 'director' => '감독자',
'system' => '서버관리자', 'developer' => '개발자', 'master' => "마스터",
]);
define('STATUS', [
"use" => "사용",
"unuse" => "사용않함",
]);
if (!is_dir(DEFAULTS['EXCEL_PATH'])) {
mkdir(DEFAULTS['EXCEL_PATH'], 0640);
}
//Upload , Download 관련
define('FILES', [
'UPLOADS' => ['mode' => 0600, 'path' => 'uploads'],
'DOWNLOADS' => ['mode' => 0600, 'path' => 'downloads'],
define('PATHS', [
'EXCEL' => getenv('path.excel') ? getenv('path.excel') : "../writable/Excel",
'UPLOADS' => getenv('path.upload') ? getenv('path.upload') : "../writable/uploads",
'DOWNLOAD' => getenv('path.download') ? getenv('path.download') : "../writable/download",
]);
if (!is_dir(APPPATH . PATHS['EXCEL'])) {
mkdir(APPPATH . PATHS['EXCEL'], 0640);
}
//아이콘 및 Sound관련
define('ICONS', [
@ -184,19 +187,15 @@ define('AUDIOS', [
'Alram_GetEmail' => '<object width=0 height=0 data="/sound/jarvis_email.mp3" type="audio/mpeg"></object>',
]);
//HPILO 관련
define(
'HPILOS',
[
'PATH' => getenv('hpilo.path') ? getenv('hpilo.path') : "../writable/HPILO",
'ADAPTER' => getenv('hpilo.adapter') ? getenv('hpilo.adapter') : "\App\Libraries\Adapter\API\GuzzleAdapter",
'DEBUG' => getenv('hpilo.debug') == 'true' ? true : false,
'SSL' => getenv('hpilo.ssl') == 'true' ? true : false,
'GUZZLE_COOKIE' => getenv('hpilo.guzzle.cookie') == 'true' ? true : false,
'CURL_COOKIE_FILE' => getenv('hpilo.curl.cookie.file') ? getenv('hpilo.curl.cookie.file') : "/cookie.txt",
'CURL_DEBUG_FILE' => getenv('hpilo.curl.debug.file') ? getenv('hpilo.curl.debug.file') : "/debug.txt",
]
);
if (!is_dir(HPILOS['PATH'])) {
mkdir(HPILOS['PATH'], 0640);
}
//Default값 정의
define('DEFAULTS', [
'ROLE' => getenv('default.role') ? getenv('default.role') : "user",
'STATUS' => getenv('default.status') ? getenv('default.status') : "use",
'EMPTY' => getenv('default.empty') ? getenv('default.empty') : "",
'PERPAGE' => getenv('default.perpage') ? getenv('default.perpage') : 20,
]);
define('UUIDS', [
'NAMESPACE' => getenv('uuid.namespace') ? getenv('uuid.namespace') : "8fc990b07418d5826d98de952cfb268dee4a23a3",
'SECRET' => getenv('uuid.secret') ? getenv('default.secret') : "delftstack1",
]);

View File

@ -30,6 +30,10 @@ $routes->setAutoRoute(false);
// We get a performance increase by specifying the default
// route since we don't have to scan directories.
//추가 RULE UUID형식
$routes->addPlaceholder('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}');
$routes->get('/', 'Home::index');
$routes->get('/login', 'Common\AuthController::login');
$routes->post('/signin', 'Common\AuthController::signin/local');
@ -49,11 +53,11 @@ $routes->group('admin', ['namespace' => 'App\Controllers\Admin', 'filter' => 'au
$routes->get('excel', 'UserController::excel');
$routes->get('insert', 'UserController::insert_form', ['filter' => 'authFilter:master,director']);
$routes->post('insert', 'UserController::insert', ['filter' => 'authFilter:master,director']);
$routes->get('update/(:num)', 'UserController::update_form/$1');
$routes->post('update/(:num)', 'UserController::update/$1');
$routes->get('view/(:num)', 'UserController::view/$1');
$routes->get('delete/(:num)', 'UserController::delete/$1', ['filter' => 'authFilter:master,director']);
$routes->get('toggle/(:num)/(:hash)', 'UserController::toggle/$1/$2', ['filter' => 'authFilter:master,director']);
$routes->get('update/(:uuid)', 'UserController::update_form/$1');
$routes->post('update/(:uuid)', 'UserController::update/$1');
$routes->get('view/(:uuid)', 'UserController::view/$1');
$routes->get('delete/(:uuid)', 'UserController::delete/$1', ['filter' => 'authFilter:master,director']);
$routes->get('toggle/(:uuid)/(:hash)', 'UserController::toggle/$1/$2', ['filter' => 'authFilter:master,director']);
$routes->post('batchjob', 'UserController::batchjob', ['filter' => 'authFilter:master,director']);
});
$routes->group('usersns', static function ($routes) {

View File

@ -16,24 +16,18 @@ class UserController extends \App\Controllers\Admin\AdminController
$this->_model = new UserModel();
$this->_defines = [
'insert' => [
'fields' => ['id', 'passwd', 'name', 'email', 'role', 'status'],
'fields' => ['id', 'passwd', 'confirmpassword', 'name', 'email', 'role', 'status'],
'fieldFilters' => ['role', 'status'],
'fieldRules' => [
'id' => 'required|min_length[4]|max_length[20]|is_unique[user.id]',
'passwd' => 'required|trim|min_length[4]|max_length[130]',
'name' => 'required|min_length[2]|max_length[20]',
'email' => 'required|valid_email',
'role' => 'required|in_list[user,manager,cloudflare,director,master]',
'id' => 'required|min_length[4]|max_length[20]|is_unique[tw_user.id]',
'confirmpassword' => 'required|trim|matches[passwd]',
]
],
'update' => [
'fields' => ['passwd', 'name', 'email', 'role', 'status'],
'fieldFilters' => ['role', 'status'],
'fieldRules' => [
'passwd' => 'required|trim|min_length[4]|max_length[30]',
'name' => 'required|min_length[2]|max_length[20]',
'email' => 'required|valid_email',
'role' => 'required|in_list[user,manager,cloudflare,director,master]',
'confirmpassword' => 'required|trim|matches[passwd]',
]
],
'view' => [

View File

@ -21,11 +21,7 @@ class UserSNSController extends \App\Controllers\Admin\AdminController
'insert' => [
'fields' => ['site', 'user_uid', 'name', 'email', 'status'],
'fieldFilters' => ['status'],
'fieldRules' => [
'name' => 'required|min_length[2]|max_length[20]',
'email' => 'required|valid_email',
'status' => 'required|in_list[use,unuse]',
]
'fieldRules' => []
],
'index' => [
'fields' => ['site', 'user_uid', 'name', 'email', 'status', 'created_at'],

View File

@ -10,8 +10,6 @@ use Psr\Log\LoggerInterface;
class CommonController extends BaseController
{
use \App\Controllers\Trait\CommonTrait;
protected $_className = '';
protected $_model = null;
protected $_defines = array();

View File

@ -1,7 +0,0 @@
<?php
namespace App\Controllers\Trait;
trait CommonTrait
{
}

View File

@ -1,53 +1,67 @@
DROP TABLE IF EXISTS user;
CREATE TABLE user (
uid int(5) unsigned NOT NULL AUTO_INCREMENT,
id varchar(20) NOT NULL,
passwd varchar(30) NOT NULL,
name varchar(20) NOT NULL,
DROP TABLE IF EXISTS tw_user;
CREATE TABLE tw_user (
uid varchar(36) NOT NULL COMMENT "사용자 UUID",
id varchar(30) NOT NULL,
passwd varchar(100) NOT NULL,
name varchar(20) NOT NULL COMMENT "사용자명",
email varchar(50) NOT NULL,
role varchar(10) NOT NULL DEFAULT 'user',
status varchar(10) NOT NULL DEFAULT 'use',
role varchar(30) NOT NULL DEFAULT 'user' COMMENT '사용자등급',
status varchar(10) NOT NULL DEFAULT 'use' COMMENT 'use: 사용,unuse: 사용않함',
updated_at timestamp NULL DEFAULT NULL,
created_at timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (uid),
UNIQUE KEY id (id),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT ='사용자 정보';
UNIQUE KEY email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT ='사용자 정보';
-- insert into tw_user (uid,id,passwd,name,email,role,status) select uuid(),id,passwd,name,email,role,status from cfmgr.user;
DROP TABLE IF EXISTS tw_user_profile;
DROP TABLE IF EXISTS user_sns;
CREATE TABLE user_sns (
uid varchar(255) NOT NULL,
user_uid int(5) unsigned NULL COMMENT 'user_uid',
site varchar(50) NOT NULL,
name varchar(20) NOT NULL,
email varchar(50) NOT NULL,
CREATE TABLE tw_user_profile (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
user_uid varchar(36) NULL COMMENT '사용자 추가정보',
type varchar(10) NOT NULL COMMENT 'ICON|ADDRESS|PHONE|MOBILE|EMAIL 등등',
content varchar(255) NULL,
priority int(3) NOT NULL DEFAULT 1 COMMENT '표시 우선순서',
status varchar(10) NOT NULL DEFAULT 'use' COMMENT 'use: 사용,unuse: 사용않함',
updated_at timestamp NULL DEFAULT NULL,
status varchar(10) NOT NULL DEFAULT 'use',
created_at timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (uid),
CONSTRAINT user_sns_ibfk_1 FOREIGN KEY (user_uid) REFERENCES user (uid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT ='SNS 사용자 정보';
CONSTRAINT FOREIGN KEY (user_uid) REFERENCES tw_user (uid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT ='사용자 추가정보';
DROP TABLE IF EXISTS logger;
DROP TABLE IF EXISTS tw_user_auth_google;
CREATE TABLE tw_user_auth_google (
uid varchar(36) NOT NULL,
id varchar(255) NOT NULL COMMENT 'sns 로그인 인중후 Return ID값',
email varchar(50) NOT NULL,
name varchar(50) NOT NULL,
detail text NOT NULL COMMENT 'JSON형식 원본값',
status varchar(10) NOT NULL DEFAULT 'use' COMMENT 'use: 사용,unuse: 사용않함',
updated_at timestamp NULL DEFAULT NULL,
created_at timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT ='GOOGLE 로그인 후 정보';
DROP TABLE IF EXISTS tw_logger;
CREATE TABLE tw_logger (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
user_uid varchar(36) NULL COMMENT '사용자 정보',
title varchar(255) NOT NULL COMMENT 'title',
content text NOT NULL COMMENT '내용',
status varchar(10) NOT NULL DEFAULT 'use',
updated_at timestamp NULL DEFAULT NULL,
created_at timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (uid)
) ENGINE = MyISAM DEFAULT CHARSET = utf8 COLLATE=utf8_unicode_ci COMMENT = 'log 정보';
DROP TABLE IF EXISTS tw_hpilo;
CREATE TABLE
logger (
uid int(5) unsigned NOT NULL AUTO_INCREMENT,
user_uid int(5) unsigned NOT NULL COMMENT 'user_uid',
title varchar(255) NOT NULL COMMENT 'title',
content text NOT NULL COMMENT '내용',
status varchar(10) NOT NULL DEFAULT 'use',
updated_at timestamp NULL DEFAULT NULL,
created_at timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (uid),
CONSTRAINT logger_ibfk_1 FOREIGN KEY (user_uid) REFERENCES user (uid) ON DELETE CASCADE
) ENGINE = MyISAM DEFAULT CHARSET = utf8 COMMENT = 'log 정보';
DROP TABLE IF EXISTS hpilo;
CREATE TABLE
hpilo (
tw_hpilo (
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
customer varchar(100) NOT NULL COMMENT '고객정보',
id varchar(20) NOT NULL DEFAULT 'Administrator' COMMENT 'API IP Address',
@ -65,4 +79,4 @@ CREATE TABLE
created_at timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (uid),
UNIQUE KEY hpilokey (ip,port)
) ENGINE = MyISAM DEFAULT CHARSET = utf8 COLLATE = utf8_general_ci COMMENT = 'hpilo 정보';
) ENGINE = MyISAM DEFAULT CHARSET = utf8 COLLATE = utf8_general_ci COMMENT = 'hpilo 정보';

View File

@ -13,15 +13,6 @@ return [
'updated_at' => "수정일",
'created_at' => "작성일"
],
"ROLE" => [
"user" => "회원",
"manager" => "관리자",
"cloudflare" => "Cloudflare관리자",
"director" => "감독자",
"master" => "마스터"
],
"STATUS" => [
"use" => "사용",
"unuse" => "사용않함",
]
"ROLE" => [...ROLES],
"STATUS" => [...STATUS],
];

View File

@ -12,9 +12,5 @@ return [
'created_at' => "작성일"
],
"USER_UID" => [],
"STATUS" => [
"use" => "사용",
"unuse" => "사용않함",
"standby" => "승인대기",
]
"STATUS" => [...STATUS, "standby" => "승인대기"],
];

View File

@ -4,10 +4,11 @@ namespace App\Models;
use CodeIgniter\Model;
use App\Libraries\Log\Log;
use App\Trait\CommonTrait;
class CommonModel extends Model
{
use Trait\CommonTrait;
use CommonTrait;
protected $DBGroup = 'default';
// protected $table = 'user';
@ -49,8 +50,10 @@ class CommonModel extends Model
Log::add("error", implode("\n", $this->errors()));
throw new \Exception(__FUNCTION__ . " 오류 발생.\n" . var_export($this->errors(), true));
}
$pk = $this->primaryKey;
$entity->$pk = $this->insertID();
if ($this->useAutoIncrement === true) {
$pk = $this->primaryKey;
$entity->$pk = $this->insertID();
}
return $entity;
}
final protected function modify_process($entity)

View File

@ -6,16 +6,16 @@ use App\Entities\LoggerEntity;
class LoggerModel extends CommonModel
{
protected $table = 'logger';
protected $table = 'tw_logger';
// protected $primaryKey = 'uid';
// protected $useAutoIncrement = true;
protected $allowedFields = ['user_uid', 'title', 'content', 'status', 'updated_at'];
protected $allowedFields = ['tw_user_uid', 'title', 'content', 'status', 'updated_at'];
protected $validationRules = [
'uid' => 'if_exist|numeric',
'user_uid' => 'if_exist|numeric',
'title' => 'if_exist|string',
'user_uid' => 'required|string',
'title' => 'required|string',
'content' => 'if_exist|string',
'status' => 'if_exist|in_list[use,unuse]',
'status' => 'if_exist|string',
'updated_at' => 'if_exist|valid_date',
'created_at' => 'if_exist|valid_date',
];
@ -32,7 +32,7 @@ class LoggerModel extends CommonModel
{
$entity = new LoggerEntity($datas);
$entity->user_uid = session()->get('uid');
return parent::modify_process($entity);
return parent::create_process($entity);
}
public function modify(LoggerEntity $entity, array $datas): LoggerEntity
{

View File

@ -1,7 +0,0 @@
<?php
namespace App\Models\Trait;
trait CommonTrait
{
}

View File

@ -6,19 +6,18 @@ use App\Entities\UserEntity;
class UserModel extends CommonModel
{
protected $table = 'user';
protected $table = 'tw_user';
// protected $primaryKey = 'uid';
// protected $useAutoIncrement = true;
protected $allowedFields = ['id', 'passwd', 'name', 'email', 'role', 'status', 'updated_at'];
protected $useAutoIncrement = false;
protected $allowedFields = ['uid', 'id', 'email', 'passwd', 'name', 'role', 'status', 'updated_at'];
protected $validationRules = [
'uid' => 'if_exist|numeric',
'id' => 'if_exist|min_length[4]|max_length[20]',
'passwd' => 'if_exist|trim|min_length[4]|max_length[150]',
'confirmpassword' => 'if_exist|trim|matches[passwd]',
'name' => 'if_exist|min_length[2]|max_length[20]',
'email' => 'if_exist|valid_email',
'role' => 'if_exist|in_list[user,manager,cloudflare,director,master]',
'status' => 'if_exist|in_list[use,unuse,standby]',
'uid' => 'if_exist|regex_match[/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/]',
'id' => 'required|min_length[4]|max_length[20]',
'passwd' => 'required|trim|min_length[4]|max_length[150]',
'name' => 'required|min_length[2]|max_length[20]',
'email' => 'required|valid_email',
'role' => 'required|string',
'status' => 'if_exist|string',
'updated_at' => 'if_exist|valid_date',
'created_at' => 'if_exist|valid_date',
];
@ -27,13 +26,14 @@ class UserModel extends CommonModel
{
return $this->asObject(UserEntity::class)->where($field, $value)->first();
}
public function getEntity(int $uid): ?UserEntity
public function getEntity(string $uid): ?UserEntity
{
return $this->getEntityByField($this->primaryKey, $uid);
}
public function create(array $datas): UserEntity
{
$entity = new UserEntity();
$entity->uid = $this->getUUIDv5_CommonTrait(UUIDS['NAMESPACE'], UUIDS['SECRET']);
foreach ($datas as $field => $value) {
$entity->$field = $field === 'passwd' ? $entity->getEncryptedPassword($value) : $value;
}

View File

@ -6,17 +6,17 @@ use App\Entities\UserSNSEntity;
class UserSNSModel extends CommonModel
{
protected $table = 'user_sns';
protected $table = 'tw_user_sns';
// protected $primaryKey = 'uid';
protected $useAutoIncrement = false;
protected $allowedFields = ['uid', 'user_uid', 'site', 'name', 'email', 'status', 'updated_at', 'created_at'];
protected $validationRules = [
'uid' => 'if_exist|min_length[4]|max_length[250]',
'user_uid' => 'if_exist|numeric',
'site' => 'if_exist|min_length[4]',
'name' => 'if_exist|min_length[2]|max_length[20]',
'email' => 'if_exist|valid_email',
'status' => 'if_exist|in_list[use,unuse,standby]',
'uid' => 'required|string',
'user_uid' => 'required|required',
'site' => 'required|string',
'name' => 'required|string',
'email' => 'required|valid_email',
'status' => 'if_exist|string',
'updated_at' => 'if_exist|valid_date',
'created_at' => 'if_exist|valid_date',
];

35
app/Trait/CommonTrait.php Normal file
View File

@ -0,0 +1,35 @@
<?php
namespace App\Trait;
trait CommonTrait
{
//참고:https://www.delftstack.com/howto/php/php-uuid/#create-a-function-to-generate-v5-uuid-in-php
// $v5_uuid = getUUIDv5_CommonTrait('8fc990b07418d5826d98de952cfb268dee4a23a3', 'delftstack!');
public function getUUIDv5_CommonTrait($name_space = '8fc990b07418d5826d98de952cfb268dee4a23a3', $string = 'delftstack1')
{
$n_hex = str_replace(array('-', '{', '}'), '', $name_space); // Getting hexadecimal components of namespace
$binray_str = ''; // Binary value string
//Namespace UUID to bits conversion
for ($i = 0; $i < strlen($n_hex); $i += 2) {
$binray_str .= chr(hexdec($n_hex[$i] . $n_hex[$i + 1]));
}
//hash value
$hashing = sha1($binray_str . $string);
return sprintf(
'%08s-%04s-%04x-%04x-%12s',
// 32 bits for the time_low
substr($hashing, 0, 8),
// 16 bits for the time_mid
substr($hashing, 8, 4),
// 16 bits for the time_hi,
(hexdec(substr($hashing, 12, 4)) & 0x0fff) | 0x5000,
// 8 bits and 16 bits for the clk_seq_hi_res,
// 8 bits for the clk_seq_low,
(hexdec(substr($hashing, 16, 4)) & 0x3fff) | 0x8000,
// 48 bits for the node
substr($hashing, 20, 12)
);
}
}

View File

@ -11,37 +11,31 @@
<h2><a href="/admin/user"><i class="fa fa-users"></i>계정관리</a></h2>
</div>
<div class="accordion-item">
<h2><a href="/admin/usersns"><i class="fa fa-users"></i>SNS계정관리</a></h2>
</div>
<div class="accordion-item">
<h2><a href="/admin/hpilo"><i class="fa fa-map-signs"></i>Server관리</a></h2>
<h2><a href="/admin/auth/google"><i class="fa fa-users"></i>GOOGLE계정관리</a></h2>
</div>
<div class="accordion-item">
<h2><a href="/admin/logger"><i class="fa fa-recycle"></i>Log관리</a></h2>
</div>
<!-- <div class="accordion-item">
<div class="accordion-item">
<h2 class="accordion-header" id="panelsStayOpen-headingOne">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#panelsStayOpen-collapseOne" aria-expanded="false" aria-controls="panelsStayOpen-collapseOne"><b>Cloudflare API</b></button>
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#panelsStayOpen-collapseOne" aria-expanded="false" aria-controls="panelsStayOpen-collapseOne"><b>상점관리</b></button>
</h2>
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse show" aria-labelledby="panelsStayOpen-headingOne">
<div class="accordion-item">
<h2><a href="/admin/cloudflare/auth"><i class="fa fa-user-secret"></i>Auth관리</a></h2>
<h2><a href="/admin/ecommerce/product"><i class="fa fa-user-secret"></i>상품 관리</a></h2>
</div>
<div class="accordion-item">
<h2><a href="/admin/cloudflare/account"><i class="fa fa-share-alt"></i>Account관리</a></h2>
<h2><a href="/admin/ecommerce/cart"><i class="fa fa-share-alt"></i>장바구니 관리</a></h2>
</div>
<div class="accordion-item">
<h2><a href="/admin/cloudflare/zone"><i class="fa fa-cubes"></i>Zone관리</a></h2>
<h2><a href="/admin/ecommerce/order"><i class="fa fa-cubes"></i>주문 관리</a></h2>
</div>
<div class="accordion-item">
<h2><a href="/admin/cloudflare/record"><i class="fa fa-cube"></i>Record관리</a></h2>
</div>
<div class="accordion-item">
<h2><a href="/admin/cloudflare/firewall"><i class="fa fa-shield"></i>Firewall관리</a></h2>
<h2><a href="/admin/ecommerce/billing"><i class="fa fa-cube"></i>결제 관리</a></h2>
</div>
</div>
</div>
<div class="accordion-item">
<!-- <div class="accordion-item">
<h2 class="accordion-header" id="panelsStayOpen-headingTwo">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#panelsStayOpen-collapseTwo" aria-expanded="false" aria-controls="panelsStayOpen-collapseTwo"><b>Magic Transit</b></button>
</h2>