域名管理
This commit is contained in:
Executable
+26
@@ -0,0 +1,26 @@
|
||||
-- 域名管理模块表结构
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP TABLE IF EXISTS `fa_domain`;
|
||||
CREATE TABLE `fa_domain` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`admin_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '管理员ID',
|
||||
`domain` varchar(255) NOT NULL DEFAULT '' COMMENT '根域名',
|
||||
`full_url` varchar(512) NOT NULL DEFAULT '' COMMENT '完整链接',
|
||||
`zone_id` varchar(64) NOT NULL DEFAULT '' COMMENT 'Cloudflare Zone ID',
|
||||
`nameservers` text COMMENT 'CF分配的NS(JSON)',
|
||||
`zone_status` enum('pending','active','failed') NOT NULL DEFAULT 'pending' COMMENT 'Zone状态',
|
||||
`ns_status` enum('pending','verified','failed') NOT NULL DEFAULT 'pending' COMMENT 'NS状态',
|
||||
`dns_status` enum('pending','created','failed') NOT NULL DEFAULT 'pending' COMMENT 'DNS状态',
|
||||
`check_time` bigint(16) DEFAULT NULL COMMENT '检测时间',
|
||||
`check_result` varchar(2000) DEFAULT '' COMMENT '最近检测结果',
|
||||
`createtime` bigint(16) DEFAULT NULL COMMENT '创建时间',
|
||||
`updatetime` bigint(16) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `domain` (`domain`),
|
||||
KEY `admin_id` (`admin_id`),
|
||||
KEY `zone_status` (`zone_status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='域名管理表';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
Regular → Executable
Executable
+247
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\model\Domain as DomainModel;
|
||||
use app\common\controller\Backend;
|
||||
use app\common\service\CloudflareService;
|
||||
use app\common\service\DomainDetectRateLimitService;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
use think\exception\PDOException;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
/**
|
||||
* 域名管理
|
||||
*
|
||||
* @icon fa fa-globe
|
||||
* @remark 域名接入Cloudflare,新增后请在详情查看NS并到注册商修改
|
||||
*/
|
||||
class Domain extends Backend
|
||||
{
|
||||
/** @var DomainModel */
|
||||
protected $model = null;
|
||||
|
||||
protected $searchFields = 'domain';
|
||||
protected $dataLimit = 'personal';
|
||||
protected $modelValidate = true;
|
||||
protected $modelSceneValidate = true;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new DomainModel();
|
||||
$this->view->assign('zoneStatusList', $this->model->getZoneStatusList());
|
||||
$this->view->assign('nsStatusList', $this->model->getNsStatusList());
|
||||
$this->view->assign('dnsStatusList', $this->model->getDnsStatusList());
|
||||
$this->assignconfig('zoneStatusList', $this->model->getZoneStatusList());
|
||||
$this->assignconfig('nsStatusList', $this->model->getNsStatusList());
|
||||
$this->assignconfig('dnsStatusList', $this->model->getDnsStatusList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止编辑
|
||||
*/
|
||||
public function edit($ids = null)
|
||||
{
|
||||
$this->error(__('Domain cannot be edited after creation'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止批量操作
|
||||
*/
|
||||
public function multi($ids = '')
|
||||
{
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加域名并创建 Cloudflare Zone
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if (false === $this->request->isPost()) {
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
$params = $this->request->post('row/a', []);
|
||||
if ($params === []) {
|
||||
$this->error(__('Parameter %s can not be empty', ''));
|
||||
}
|
||||
|
||||
$params = $this->preExcludeFields($params);
|
||||
$domain = DomainModel::normalizeDomain((string)($params['domain'] ?? ''));
|
||||
$params['domain'] = $domain;
|
||||
|
||||
if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
|
||||
$params[$this->dataLimitField] = $this->auth->id;
|
||||
}
|
||||
|
||||
$result = false;
|
||||
Db::startTrans();
|
||||
try {
|
||||
if ($this->modelValidate) {
|
||||
$name = str_replace('\\model\\', '\\validate\\', get_class($this->model));
|
||||
$validate = $this->modelSceneValidate ? $name . '.add' : $name;
|
||||
$this->model->validateFailException()->validate($validate, $params);
|
||||
}
|
||||
|
||||
if (DomainModel::where('domain', $domain)->find()) {
|
||||
throw new ValidateException(__('Domain already exists'));
|
||||
}
|
||||
|
||||
$cloudflare = new CloudflareService();
|
||||
$zone = $cloudflare->createZone($domain);
|
||||
|
||||
$params['full_url'] = 'https://' . $domain;
|
||||
$params['zone_id'] = $zone['zone_id'];
|
||||
$params['nameservers'] = $zone['name_servers'];
|
||||
$params['zone_status'] = DomainModel::mapCloudflareZoneStatus($zone['status']);
|
||||
$params['ns_status'] = 'pending';
|
||||
$params['dns_status'] = 'pending';
|
||||
$params['check_result'] = '';
|
||||
|
||||
$result = $this->model->allowField(true)->save($params);
|
||||
Db::commit();
|
||||
} catch (ValidateException $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
} catch (PDOException|Exception $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
if ($result === false) {
|
||||
$this->error(__('No rows were inserted'));
|
||||
}
|
||||
|
||||
$this->success(__('Domain submitted, please check NS in detail page and update at registrar'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 域名详情
|
||||
*/
|
||||
public function detail($ids = null)
|
||||
{
|
||||
$row = $this->model->get($ids);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
if (!$this->checkDataLimit($row)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
$data = $row->toArray();
|
||||
$data['nameservers_list'] = $row->getNameserversArray();
|
||||
$this->view->assign('row', $data);
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测域名状态
|
||||
*/
|
||||
public function detect($ids = null)
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
|
||||
$row = $this->model->get($ids);
|
||||
if (!$row) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
if (!$this->checkDataLimit($row)) {
|
||||
$this->error(__('You have no permission'));
|
||||
}
|
||||
|
||||
try {
|
||||
(new DomainDetectRateLimitService())->assertCanDetect((int)$this->auth->id, (int)$row['id']);
|
||||
} catch (Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$cloudflare = new CloudflareService();
|
||||
$detect = $cloudflare->detectDomain($row->toArray());
|
||||
$row->save([
|
||||
'zone_status' => $detect['zone_status'],
|
||||
'ns_status' => $detect['ns_status'],
|
||||
'dns_status' => $detect['dns_status'],
|
||||
'check_time' => time(),
|
||||
'check_result' => $detect['check_result'],
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$this->success(__('Detection completed'), null, $row->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除(需输入完整域名二次确认)
|
||||
*/
|
||||
public function del($ids = '')
|
||||
{
|
||||
if (!$this->request->isPost()) {
|
||||
$this->error(__('Invalid parameters'));
|
||||
}
|
||||
|
||||
$ids = $ids ?: $this->request->post('ids', '');
|
||||
$confirmDomain = trim((string)$this->request->post('confirm_domain', ''));
|
||||
if ($ids === '' || $confirmDomain === '') {
|
||||
$this->error(__('Please enter the full domain to confirm deletion'));
|
||||
}
|
||||
|
||||
$pk = $this->model->getPk();
|
||||
$list = $this->model->where($pk, 'in', $ids)->select();
|
||||
if (!$list || count($list) === 0) {
|
||||
$this->error(__('No Results were found'));
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
Db::startTrans();
|
||||
try {
|
||||
foreach ($list as $item) {
|
||||
if (!$this->checkDataLimit($item)) {
|
||||
throw new Exception(__('You have no permission'));
|
||||
}
|
||||
if (DomainModel::normalizeDomain($confirmDomain) !== DomainModel::normalizeDomain((string)$item['domain'])) {
|
||||
throw new Exception(__('Domain confirmation does not match'));
|
||||
}
|
||||
|
||||
if (!empty($item['zone_id'])) {
|
||||
$cloudflare = new CloudflareService();
|
||||
$cloudflare->deleteZone((string)$item['zone_id']);
|
||||
}
|
||||
|
||||
$count += $item->delete();
|
||||
}
|
||||
Db::commit();
|
||||
} catch (PDOException|Exception $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
if ($count > 0) {
|
||||
$this->success();
|
||||
}
|
||||
$this->error(__('No rows were deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DomainModel $row
|
||||
*/
|
||||
protected function checkDataLimit($row): bool
|
||||
{
|
||||
if (!$this->dataLimit) {
|
||||
return true;
|
||||
}
|
||||
$adminIds = $this->getDataLimitAdminIds();
|
||||
if (!is_array($adminIds)) {
|
||||
return true;
|
||||
}
|
||||
return in_array((int)$row[$this->dataLimitField], $adminIds, true);
|
||||
}
|
||||
}
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Domain' => '域名',
|
||||
'Full_url' => '完整链接',
|
||||
'Zone_status' => 'Zone状态',
|
||||
'Ns_status' => 'NameServer状态',
|
||||
'Dns_status' => 'DNS状态',
|
||||
'Nameservers' => 'NameServer',
|
||||
'Check_time' => '检测时间',
|
||||
'Check_result' => '最近检测结果',
|
||||
'Createtime' => '创建时间',
|
||||
'Zone pending' => '待激活',
|
||||
'Zone active' => '已激活',
|
||||
'Zone failed' => '异常',
|
||||
'NS pending' => '待验证',
|
||||
'NS verified' => '已验证',
|
||||
'NS failed' => '验证失败',
|
||||
'DNS pending' => '待创建',
|
||||
'DNS created' => '已创建',
|
||||
'DNS failed' => '异常',
|
||||
'Detect' => '检测',
|
||||
'Detection completed' => '检测完成',
|
||||
'Domain input tips title' => '填写说明',
|
||||
'Domain input tips line1' => '请输入根域名,比如:nihao.com,不要填写 www,且不支持二级子域名。',
|
||||
'Domain input tips line2' => '不支持中国域名注册商注册的域名,比如阿里云、腾讯云等注册商;也不支持中国域名后缀,包括 cn、com.cn、net.cn、org.cn 等。',
|
||||
'Domain guide title' => '接入指引',
|
||||
'Domain guide content' => '提交后,请在详情中查看 Cloudflare 实际分配的 NS,并到域名注册商处修改。',
|
||||
'Domain submitted, please check NS in detail page and update at registrar' => '域名已提交,请在详情中查看 Cloudflare 分配的 NS,并到注册商处修改',
|
||||
'Domain cannot be edited after creation' => '域名创建后不可编辑',
|
||||
'Domain already exists' => '该域名已存在',
|
||||
'Please enter the full domain to confirm deletion' => '请输入完整域名以确认删除',
|
||||
'Domain confirmation does not match' => '输入的域名与记录不一致,删除已取消',
|
||||
'Delete domain confirm prompt' => '请输入完整域名以确认删除',
|
||||
'Delete domain confirm mismatch' => '域名输入不一致,请重新确认',
|
||||
];
|
||||
Executable
+189
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 域名管理模型
|
||||
*/
|
||||
class Domain extends Model
|
||||
{
|
||||
/** @var string 中国域名后缀黑名单 */
|
||||
public const CHINA_SUFFIX_BLACKLIST = [
|
||||
'cn', 'com.cn', 'net.cn', 'org.cn', 'gov.cn', 'edu.cn', 'ac.cn', 'mil.cn',
|
||||
];
|
||||
|
||||
protected $name = 'domain';
|
||||
protected $autoWriteTimestamp = 'integer';
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
protected $deleteTime = false;
|
||||
|
||||
protected $append = [
|
||||
'zone_status_text',
|
||||
'ns_status_text',
|
||||
'dns_status_text',
|
||||
];
|
||||
|
||||
public function getZoneStatusList(): array
|
||||
{
|
||||
return [
|
||||
'pending' => __('Zone pending'),
|
||||
'active' => __('Zone active'),
|
||||
'failed' => __('Zone failed'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getNsStatusList(): array
|
||||
{
|
||||
return [
|
||||
'pending' => __('NS pending'),
|
||||
'verified' => __('NS verified'),
|
||||
'failed' => __('NS failed'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getDnsStatusList(): array
|
||||
{
|
||||
return [
|
||||
'pending' => __('DNS pending'),
|
||||
'created' => __('DNS created'),
|
||||
'failed' => __('DNS failed'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getZoneStatusTextAttr($value, $data): string
|
||||
{
|
||||
$value = $value ?: ($data['zone_status'] ?? '');
|
||||
$list = $this->getZoneStatusList();
|
||||
return $list[$value] ?? '';
|
||||
}
|
||||
|
||||
public function getNsStatusTextAttr($value, $data): string
|
||||
{
|
||||
$value = $value ?: ($data['ns_status'] ?? '');
|
||||
$list = $this->getNsStatusList();
|
||||
return $list[$value] ?? '';
|
||||
}
|
||||
|
||||
public function getDnsStatusTextAttr($value, $data): string
|
||||
{
|
||||
$value = $value ?: ($data['dns_status'] ?? '');
|
||||
$list = $this->getDnsStatusList();
|
||||
return $list[$value] ?? '';
|
||||
}
|
||||
|
||||
public function setNameserversAttr($value): string
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return json_encode(array_values($value), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getNameserversArray(): array
|
||||
{
|
||||
$value = $this->getAttr('nameservers');
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_string($value) && $value !== '') {
|
||||
$decoded = json_decode($value, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function normalizeDomain(string $domain): string
|
||||
{
|
||||
return strtolower(trim($domain));
|
||||
}
|
||||
|
||||
public static function isValidRootDomain(string $domain): bool
|
||||
{
|
||||
$domain = self::normalizeDomain($domain);
|
||||
if ($domain === '' || strlen($domain) > 253) {
|
||||
return false;
|
||||
}
|
||||
if (strpos($domain, 'www.') === 0) {
|
||||
return false;
|
||||
}
|
||||
if (!preg_match('/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i', $domain)) {
|
||||
return false;
|
||||
}
|
||||
if (self::isChinaSuffix($domain)) {
|
||||
return false;
|
||||
}
|
||||
$label = self::getDomainLabelWithoutSuffix($domain);
|
||||
if ($label === '' || strpos($label, '.') !== false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isChinaSuffix(string $domain): bool
|
||||
{
|
||||
$domain = self::normalizeDomain($domain);
|
||||
foreach (self::CHINA_SUFFIX_BLACKLIST as $suffix) {
|
||||
if ($domain === $suffix || self::endsWith($domain, '.' . $suffix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getMatchedSuffix(string $domain): string
|
||||
{
|
||||
$domain = self::normalizeDomain($domain);
|
||||
$suffixes = array_merge(self::CHINA_SUFFIX_BLACKLIST, ['co.uk', 'com', 'net', 'org', 'io', 'co', 'me', 'info', 'biz']);
|
||||
usort($suffixes, function ($a, $b) {
|
||||
return strlen($b) - strlen($a);
|
||||
});
|
||||
foreach ($suffixes as $suffix) {
|
||||
if ($domain === $suffix || self::endsWith($domain, '.' . $suffix)) {
|
||||
return $suffix;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function getDomainLabelWithoutSuffix(string $domain): string
|
||||
{
|
||||
$domain = self::normalizeDomain($domain);
|
||||
$suffix = self::getMatchedSuffix($domain);
|
||||
if ($suffix === '') {
|
||||
$pos = strrpos($domain, '.');
|
||||
return $pos === false ? $domain : substr($domain, 0, $pos);
|
||||
}
|
||||
$suffixWithDot = '.' . $suffix;
|
||||
if (self::endsWith($domain, $suffixWithDot)) {
|
||||
return substr($domain, 0, -strlen($suffixWithDot));
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
|
||||
public static function mapCloudflareZoneStatus(string $status): string
|
||||
{
|
||||
if ($status === 'active') {
|
||||
return 'active';
|
||||
}
|
||||
if (in_array($status, ['pending', 'initializing'], true)) {
|
||||
return 'pending';
|
||||
}
|
||||
return 'failed';
|
||||
}
|
||||
|
||||
private static function endsWith(string $haystack, string $needle): bool
|
||||
{
|
||||
if ($needle === '') {
|
||||
return true;
|
||||
}
|
||||
return substr($haystack, -strlen($needle)) === $needle;
|
||||
}
|
||||
}
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\admin\validate;
|
||||
|
||||
use app\admin\model\Domain as DomainModel;
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 域名验证器
|
||||
*/
|
||||
class Domain extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'domain' => 'require|checkDomain',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'domain.require' => '请输入根域名',
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['domain'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function checkDomain($value)
|
||||
{
|
||||
$domain = DomainModel::normalizeDomain((string)$value);
|
||||
if (!DomainModel::isValidRootDomain($domain)) {
|
||||
return '请输入合法根域名,不要填写 www 或二级子域名,且不支持中国域名后缀';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
|
||||
{:token()}
|
||||
|
||||
<div class="alert alert-warning-light">
|
||||
<p><strong>{:__('Domain input tips title')}</strong></p>
|
||||
<p>{:__('Domain input tips line1')}</p>
|
||||
<p>{:__('Domain input tips line2')}</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info-light">
|
||||
<p><strong>{:__('Domain guide title')}</strong></p>
|
||||
<p>{:__('Domain guide content')}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="c-domain" class="control-label col-xs-12 col-sm-2">{:__('Domain')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input id="c-domain" data-rule="required" class="form-control" name="row[domain]" type="text" value="" placeholder="nihao.com">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
|
||||
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
<style>
|
||||
.table-domain-detail tr td { word-break: break-all; }
|
||||
.domain-ns-list { margin: 0; padding-left: 18px; }
|
||||
.domain-ns-list li { margin-bottom: 6px; font-family: Menlo, Monaco, Consolas, monospace; }
|
||||
</style>
|
||||
<table class="table table-striped table-domain-detail">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="140">{:__('Domain')}</td>
|
||||
<td>{$row.domain|htmlentities}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{:__('Full_url')}</td>
|
||||
<td><a href="{$row.full_url|htmlentities}" target="_blank" rel="noopener noreferrer">{$row.full_url|htmlentities}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{:__('Zone_status')}</td>
|
||||
<td>{$row.zone_status_text|htmlentities}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{:__('Ns_status')}</td>
|
||||
<td>{$row.ns_status_text|htmlentities}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{:__('Dns_status')}</td>
|
||||
<td>{$row.dns_status_text|htmlentities}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{:__('Nameservers')}</td>
|
||||
<td>
|
||||
{if !empty($row.nameservers_list)}
|
||||
<ul class="domain-ns-list">
|
||||
{volist name="row.nameservers_list" id="ns"}
|
||||
<li>{$ns|htmlentities}</li>
|
||||
{/volist}
|
||||
</ul>
|
||||
<p class="text-muted">{:__('Domain guide content')}</p>
|
||||
{else/}
|
||||
<span class="text-muted">{:__('None')}</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{:__('Check_time')}</td>
|
||||
<td>{if $row.check_time}{$row.check_time|datetime}{else/}{:__('None')}{/if}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{:__('Check_result')}</td>
|
||||
<td>{$row.check_result|default=''|htmlentities}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{:__('Createtime')}</td>
|
||||
<td>{$row.createtime|datetime}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="hide layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="reset" class="btn btn-primary btn-embossed btn-close" onclick="Layer.closeAll();">{:__('Close')}</button>
|
||||
</div>
|
||||
</div>
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
<div class="panel panel-default panel-intro">
|
||||
{:build_heading()}
|
||||
|
||||
<div class="panel-body">
|
||||
<div id="myTabContent" class="tab-content">
|
||||
<div class="tab-pane fade active in" id="one">
|
||||
<div class="widget-body no-padding">
|
||||
<div id="toolbar" class="toolbar">
|
||||
{:build_toolbar('refresh,add')}
|
||||
</div>
|
||||
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
|
||||
data-operate-edit="false"
|
||||
data-operate-del="false"
|
||||
width="100%">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user