mirror of
https://github.com/netcccyun/dnsmgr.git
synced 2026-05-09 23:16:27 +02:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0eb096873 | ||
|
|
ebdc34cf4b | ||
|
|
b19cabcbfd | ||
|
|
64b5221787 | ||
|
|
41e719720c | ||
|
|
16a9c03b6c | ||
|
|
1beb731a6e | ||
|
|
6b026ce4e4 | ||
|
|
96ff262333 | ||
|
|
17ffe5704f | ||
|
|
d4c11b520f | ||
|
|
ba418da84c | ||
|
|
b58db855ca | ||
|
|
3bd45367b0 | ||
|
|
a22bc4fa37 |
@@ -182,6 +182,7 @@ class Domain extends BaseController
|
||||
$kw = input('post.kw', null, 'trim');
|
||||
$type = input('post.type', null, 'trim');
|
||||
$status = input('post.status', null, 'trim');
|
||||
$order = input('post.order', null, 'trim');
|
||||
$offset = input('post.offset/d', 0);
|
||||
$limit = input('post.limit/d', 10);
|
||||
|
||||
@@ -203,7 +204,23 @@ class Domain extends BaseController
|
||||
}
|
||||
}
|
||||
$total = $select->count();
|
||||
$rows = $select->fieldRaw('A.*,B.type,B.remark aremark')->order('A.id', 'desc')->limit($offset, $limit)->select();
|
||||
switch ($order) {
|
||||
case '1':
|
||||
$select->order('A.regtime', 'asc');
|
||||
break;
|
||||
case '2':
|
||||
$select->order('A.regtime', 'desc');
|
||||
break;
|
||||
case '3':
|
||||
$select->order('A.expiretime', 'asc');
|
||||
break;
|
||||
case '4':
|
||||
$select->order('A.expiretime', 'desc');
|
||||
break;
|
||||
default:
|
||||
$select->order('A.id', 'desc');
|
||||
}
|
||||
$rows = $select->fieldRaw('A.*,B.type,B.remark aremark')->limit($offset, $limit)->select();
|
||||
|
||||
$list = [];
|
||||
foreach ($rows as $row) {
|
||||
@@ -321,6 +338,12 @@ class Domain extends BaseController
|
||||
Db::name('optimizeip')->where('did', 'in', $ids)->delete();
|
||||
Db::name('sctask')->where('did', 'in', $ids)->delete();
|
||||
return json(['code' => 0, 'msg' => '成功删除' . count($ids) . '个域名!']);
|
||||
} elseif ($act == 'updateexpire') {
|
||||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||||
$ids = input('post.ids');
|
||||
if (empty($ids)) return json(['code' => -1, 'msg' => '参数不能为空']);
|
||||
$count = Db::name('domain')->where('id', 'in', $ids)->update(['checkstatus' => 0]);
|
||||
return json(['code' => 0, 'msg' => '已提交' . $count . '个域名,约' . ceil($count / 5) . '分钟后刷新完成。']);
|
||||
}
|
||||
return json(['code' => -3]);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class Index extends BaseController
|
||||
'framework_version' => app()->version(),
|
||||
'php_version' => PHP_VERSION,
|
||||
'mysql_version' => $mysqlVersion,
|
||||
'software' => $_SERVER['SERVER_SOFTWARE'],
|
||||
'software' => $_SERVER['SERVER_SOFTWARE'] ?? '未知',
|
||||
'os' => php_uname(),
|
||||
'date' => date("Y-m-d H:i:s"),
|
||||
];
|
||||
|
||||
@@ -559,7 +559,7 @@ class DeployHelper
|
||||
'icon' => 'opanel.png',
|
||||
'desc' => '更新面板证书管理内的SSL证书',
|
||||
'note' => null,
|
||||
'tasknote' => '系统会根据关联SSL证书的域名,自动更新对应证书',
|
||||
'tasknote' => '',
|
||||
'inputs' => [
|
||||
'url' => [
|
||||
'name' => '面板地址',
|
||||
@@ -581,7 +581,7 @@ class DeployHelper
|
||||
'v1' => '1.x',
|
||||
'v2' => '2.x',
|
||||
],
|
||||
'value' => 'v1',
|
||||
'value' => 'v2',
|
||||
'required' => true,
|
||||
],
|
||||
'proxy' => [
|
||||
@@ -594,7 +594,32 @@ class DeployHelper
|
||||
'value' => '0'
|
||||
],
|
||||
],
|
||||
'taskinputs' => [],
|
||||
'taskinputs' => [
|
||||
'type' => [
|
||||
'name' => '部署类型',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '更新已有证书',
|
||||
'3' => '面板本身的证书',
|
||||
],
|
||||
'value' => '0',
|
||||
'required' => true,
|
||||
],
|
||||
'id' => [
|
||||
'name' => '证书ID',
|
||||
'type' => 'input',
|
||||
'placeholder' => '在证书列表查看ID',
|
||||
'note' => '留空为根据关联SSL证书的域名,自动更新对应证书',
|
||||
'show' => 'type==0',
|
||||
],
|
||||
'node_name' => [
|
||||
'name' => '子节点名称',
|
||||
'type' => 'textarea',
|
||||
'placeholder' => '每行一个节点名称',
|
||||
'note' => '不填写时,将替换主控节点证书;否则,将替换被控节点证书。多个节点请每行填写一个',
|
||||
'show' => 'type==0',
|
||||
],
|
||||
],
|
||||
],
|
||||
'mwpanel' => [
|
||||
'name' => 'MW面板',
|
||||
@@ -1199,6 +1224,7 @@ ctrl+x 保存退出',
|
||||
['value'=>'tse', 'label'=>'云原生API网关TSE'],
|
||||
['value'=>'tcb', 'label'=>'云开发TCB'],
|
||||
['value'=>'lighthouse', 'label'=>'轻量应用服务器'],
|
||||
['value'=>'update', 'label'=>'更新证书内容(证书ID不变)'],
|
||||
],
|
||||
'value' => 'cdn',
|
||||
'required' => true,
|
||||
@@ -1302,6 +1328,14 @@ ctrl+x 保存退出',
|
||||
'note' => 'CDN、EO、WAF多个域名可用,隔开,其他只能填写1个域名',
|
||||
'required' => true,
|
||||
],
|
||||
'cert_id' => [
|
||||
'name' => '证书ID',
|
||||
'type' => 'input',
|
||||
'placeholder' => '要更新的证书ID,在我的证书列表查看',
|
||||
'show' => 'product==\'update\'',
|
||||
'required' => true,
|
||||
'note' => '当前接口需联系加白使用',
|
||||
],
|
||||
],
|
||||
],
|
||||
'huawei' => [
|
||||
@@ -2248,6 +2282,12 @@ ctrl+x 保存退出',
|
||||
'required' => true,
|
||||
'show' => 'auth==1',
|
||||
],
|
||||
'passphrase' => [
|
||||
'name' => '私钥密码',
|
||||
'type' => 'input',
|
||||
'placeholder' => '若私钥有设置密码,请填写此项',
|
||||
'show' => 'auth==1',
|
||||
],
|
||||
'windows' => [
|
||||
'name' => '是否Windows',
|
||||
'type' => 'radio',
|
||||
|
||||
@@ -162,6 +162,20 @@ class DnsHelper
|
||||
'page' => true,
|
||||
'add' => true,
|
||||
],
|
||||
'spaceship' => [
|
||||
'name' => 'Spaceship',
|
||||
'config' => [
|
||||
'ak' => 'AccessKey',
|
||||
'sk' => 'SecretKey',
|
||||
],
|
||||
'remark' => 0,
|
||||
'status' => false,
|
||||
'redirect' => true,
|
||||
'log' => false,
|
||||
'weight' => false,
|
||||
'page' => false,
|
||||
'add' => true,
|
||||
],
|
||||
];
|
||||
|
||||
public static $line_name = [
|
||||
@@ -176,6 +190,7 @@ class DnsHelper
|
||||
'cloudflare' => ['DEF' => '0'],
|
||||
'namesilo' => ['DEF' => 'default'],
|
||||
'powerdns' => ['DEF' => 'default'],
|
||||
'spaceship' => ['DEF' => 'default'],
|
||||
];
|
||||
|
||||
public static function getList()
|
||||
|
||||
@@ -311,7 +311,20 @@ class aliyun implements DeployInterface
|
||||
}
|
||||
|
||||
$data['Listen']['CertId'] = $cert_id;
|
||||
if (empty($data['Listen']['HttpsPorts'])) $data['Listen']['HttpsPorts'] = [443];
|
||||
if (empty($data['Listen']['HttpsPorts'])) {
|
||||
$data['Listen']['HttpsPorts'] = [443];
|
||||
$data['Listen']['TLSVersion'] = 'tlsv1.1';
|
||||
$data['Listen']['EnableTLSv3'] = true;
|
||||
$data['Listen']['CipherSuite'] = 1;
|
||||
}
|
||||
if (count($data['Redirect']['BackendPorts']) == 1 && $data['Redirect']['BackendPorts'][0]['Protocol'] == 'http') {
|
||||
$data['Redirect']['BackendPorts'][] = [
|
||||
'ListenPort' => 443,
|
||||
'Protocol' => 'https',
|
||||
'BackendPort' => $data['Redirect']['BackendPorts'][0]['BackendPort'],
|
||||
];
|
||||
$data['Redirect']['FocusHttpBackend'] = true;
|
||||
}
|
||||
$data['Redirect']['Backends'] = $data['Redirect']['AllBackends'];
|
||||
$param = [
|
||||
'Action' => 'ModifyDomain',
|
||||
|
||||
@@ -27,15 +27,108 @@ class opanel implements DeployInterface
|
||||
|
||||
public function deploy($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
// 解析节点名称列表
|
||||
$nodeNames = $this->parseNodeNames($config);
|
||||
|
||||
if (isset($config['type']) && $config['type'] == '3') {
|
||||
// 面板本身的证书部署
|
||||
$params = [
|
||||
'cert' => $fullchain,
|
||||
'key' => $privatekey,
|
||||
'ssl' => 'Enable',
|
||||
'sslID' => null,
|
||||
'sslType' => 'import-paste',
|
||||
];
|
||||
|
||||
if (empty($nodeNames)) {
|
||||
// 没有指定节点,部署到主控节点
|
||||
try {
|
||||
$this->request('/core/settings/ssl/update', $params);
|
||||
$this->log("面板证书更新成功!");
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
throw new Exception("面板证书更新失败:" . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
// 部署到多个子节点
|
||||
$successCount = 0;
|
||||
$failCount = 0;
|
||||
foreach ($nodeNames as $nodeName) {
|
||||
try {
|
||||
$this->request('/core/settings/ssl/update', $params, $nodeName);
|
||||
$this->log("节点 [{$nodeName}] 面板证书更新成功!");
|
||||
$successCount++;
|
||||
} catch (Exception $e) {
|
||||
$this->log("节点 [{$nodeName}] 面板证书更新失败:" . $e->getMessage());
|
||||
$failCount++;
|
||||
}
|
||||
}
|
||||
if ($failCount > 0 && $successCount == 0) {
|
||||
throw new Exception("所有节点证书更新失败");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有指定节点,则部署到主控节点
|
||||
if (empty($nodeNames)) {
|
||||
$this->deployToNode($fullchain, $privatekey, $config, null);
|
||||
} else {
|
||||
// 部署到多个子节点
|
||||
$successCount = 0;
|
||||
$failCount = 0;
|
||||
foreach ($nodeNames as $nodeName) {
|
||||
try {
|
||||
$this->deployToNode($fullchain, $privatekey, $config, $nodeName);
|
||||
$successCount++;
|
||||
} catch (Exception $e) {
|
||||
$this->log("节点 [{$nodeName}] 部署失败:" . $e->getMessage());
|
||||
$failCount++;
|
||||
}
|
||||
}
|
||||
if ($failCount > 0 && $successCount == 0) {
|
||||
throw new Exception("所有节点部署失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 部署到指定节点
|
||||
*/
|
||||
private function deployToNode($fullchain, $privatekey, $config, $nodeName = null)
|
||||
{
|
||||
if (!empty($config['id'])) {
|
||||
// 指定证书ID的情况
|
||||
$params = [
|
||||
'sslID' => intval($config['id']),
|
||||
'type' => 'paste',
|
||||
'certificate' => $fullchain,
|
||||
'privateKey' => $privatekey,
|
||||
'description' => '',
|
||||
];
|
||||
try {
|
||||
$this->request('/websites/ssl/upload', $params, $nodeName);
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$config['id']}更新成功!" : "证书ID:{$config['id']}更新成功!";
|
||||
$this->log($logMsg);
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$config['id']}更新失败:" : "证书ID:{$config['id']}更新失败:";
|
||||
throw new Exception($logMsg . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 根据域名自动匹配证书
|
||||
$domains = $config['domainList'];
|
||||
if (empty($domains)) throw new Exception('没有设置要部署的域名');
|
||||
|
||||
$params = ['page' => 1, 'pageSize' => 500];
|
||||
try {
|
||||
$data = $this->request("/websites/ssl/search", $params);
|
||||
$this->log('获取证书列表成功(total=' . $data['total'] . ')');
|
||||
$data = $this->request("/websites/ssl/search", $params, $nodeName);
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] " : "";
|
||||
$this->log($logMsg . '获取证书列表成功(total=' . $data['total'] . ')');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('获取证书列表失败:' . $e->getMessage());
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] " : "";
|
||||
throw new Exception($logMsg . '获取证书列表失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$success = 0;
|
||||
@@ -62,18 +155,29 @@ class opanel implements DeployInterface
|
||||
'description' => '',
|
||||
];
|
||||
try {
|
||||
$this->request('/websites/ssl/upload', $params);
|
||||
$this->log("证书ID:{$row['id']}更新成功!");
|
||||
$this->request('/websites/ssl/upload', $params, $nodeName);
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$row['id']}更新成功!" : "证书ID:{$row['id']}更新成功!";
|
||||
$this->log($logMsg);
|
||||
$success++;
|
||||
} catch (Exception $e) {
|
||||
$errmsg = $e->getMessage();
|
||||
$this->log("证书ID:{$row['id']}更新失败:" . $errmsg);
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$row['id']}更新失败:" : "证书ID:{$row['id']}更新失败:";
|
||||
$this->log($logMsg . $errmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($success == 0) {
|
||||
throw new Exception($errmsg ? $errmsg : '没有要更新的证书');
|
||||
$params = [
|
||||
'sslID' => 0,
|
||||
'type' => 'paste',
|
||||
'certificate' => $fullchain,
|
||||
'privateKey' => $privatekey,
|
||||
'description' => '',
|
||||
];
|
||||
$this->request('/websites/ssl/upload', $params, $nodeName);
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书上传成功!" : "证书上传成功!";
|
||||
$this->log($logMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +193,32 @@ class opanel implements DeployInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function request($path, $params = null)
|
||||
/**
|
||||
* 解析节点名称列表
|
||||
*/
|
||||
private function parseNodeNames($config)
|
||||
{
|
||||
if (!isset($config['node_name']) || empty($config['node_name'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$nodeNameStr = trim($config['node_name']);
|
||||
if (empty($nodeNameStr)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 按行分割,过滤空行
|
||||
$nodeNames = array_filter(
|
||||
array_map('trim', explode("\n", $nodeNameStr)),
|
||||
function($name) {
|
||||
return !empty($name);
|
||||
}
|
||||
);
|
||||
|
||||
return array_values($nodeNames);
|
||||
}
|
||||
|
||||
private function request($path, $params = null, $nodeName = null)
|
||||
{
|
||||
$url = $this->url . $path;
|
||||
|
||||
@@ -97,8 +226,11 @@ class opanel implements DeployInterface
|
||||
$token = md5('1panel' . $this->key . $timestamp);
|
||||
$headers = [
|
||||
'1Panel-Token' => $token,
|
||||
'1Panel-Timestamp' => $timestamp
|
||||
'1Panel-Timestamp' => $timestamp,
|
||||
];
|
||||
if (!empty($nodeName)) {
|
||||
$headers['CurrentNode'] = $nodeName;
|
||||
}
|
||||
$body = $params ? json_encode($params) : '{}';
|
||||
if ($body) $headers['Content-Type'] = 'application/json';
|
||||
$response = http_request($url, $body, null, null, $headers, $this->proxy);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace app\lib\deploy;
|
||||
|
||||
use app\lib\CertHelper;
|
||||
use app\lib\DeployInterface;
|
||||
use Exception;
|
||||
|
||||
@@ -49,7 +50,8 @@ class ssh implements DeployInterface
|
||||
fclose($stream);
|
||||
$this->log('私钥已保存到:' . $config['pem_key_file']);
|
||||
} elseif ($config['format'] == 'pfx') {
|
||||
$pfx = \app\lib\CertHelper::getPfx($fullchain, $privatekey, $config['pfx_pass'] ? $config['pfx_pass'] : null);
|
||||
$pfx_pass = $config['pfx_pass'] ?? null;
|
||||
$pfx = CertHelper::getPfx($fullchain, $privatekey, $pfx_pass);
|
||||
|
||||
$stream = fopen("ssh2.sftp://$sftp{$config['pfx_file']}", 'w');
|
||||
if (!$stream) {
|
||||
@@ -157,8 +159,14 @@ class ssh implements DeployInterface
|
||||
file_put_contents($privateKeyPath, $this->config['privatekey']);
|
||||
file_put_contents($publicKeyPath, $publicKey);
|
||||
umask($umask);
|
||||
if (!ssh2_auth_pubkey_file($connection, $this->config['username'], $publicKeyPath, $privateKeyPath)) {
|
||||
throw new Exception('私钥认证失败');
|
||||
if (!empty($this->config['passphrase'])) {
|
||||
if (!ssh2_auth_pubkey_file($connection, $this->config['username'], $publicKeyPath, $privateKeyPath, $this->config['passphrase'])) {
|
||||
throw new Exception('私钥认证失败');
|
||||
}
|
||||
} else {
|
||||
if (!ssh2_auth_pubkey_file($connection, $this->config['username'], $publicKeyPath, $privateKeyPath)) {
|
||||
throw new Exception('私钥认证失败');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!ssh2_auth_password($connection, $this->config['username'], $this->config['password'])) {
|
||||
|
||||
@@ -31,6 +31,9 @@ class tencent implements DeployInterface
|
||||
|
||||
public function deploy($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
if ($config['product'] == 'update') {
|
||||
return $this->update_cert($fullchain, $privatekey, $config);
|
||||
}
|
||||
$cert_id = $this->get_cert_id($fullchain, $privatekey);
|
||||
if (!$cert_id) throw new Exception('证书ID获取失败');
|
||||
if ($config['product'] == 'cos') {
|
||||
@@ -281,6 +284,95 @@ class tencent implements DeployInterface
|
||||
$this->log('边缘安全加速域名 ' . $config['domain'] . ' 部署证书成功!');
|
||||
}
|
||||
|
||||
private function update_cert($fullchain, $privatekey, $config)
|
||||
{
|
||||
if (empty($config['cert_id'])) throw new Exception('证书ID不能为空');
|
||||
|
||||
$param = [
|
||||
'CertificateIds' => [$config['cert_id']],
|
||||
'IsCache' => 1,
|
||||
];
|
||||
try {
|
||||
$data = $this->client->request('CreateCertificateBindResourceSyncTask', $param);
|
||||
if (empty($data['CertTaskIds'])) throw new Exception('返回任务ID为空');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('创建关联云资源查询任务失败:' . $e->getMessage());
|
||||
}
|
||||
$task_id = $data['CertTaskIds'][0]['TaskId'];
|
||||
$this->log('创建关联云资源查询任务成功 TaskId=' . $task_id);
|
||||
|
||||
$retry = 0;
|
||||
$resource_result = null;
|
||||
while ($retry++ < 30) {
|
||||
sleep(2);
|
||||
$param = [
|
||||
'TaskIds' => [$task_id],
|
||||
];
|
||||
try {
|
||||
$data = $this->client->request('DescribeCertificateBindResourceTaskResult', $param);
|
||||
if (empty($data['SyncTaskBindResourceResult'])) throw new Exception('返回结果为空');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('查询关联云资源任务结果失败:' . $e->getMessage());
|
||||
}
|
||||
$taskResult = $data['SyncTaskBindResourceResult'][0];
|
||||
if ($taskResult['Status'] == 1) {
|
||||
$resource_result = $taskResult['BindResourceResult'];
|
||||
break;
|
||||
} elseif ($taskResult['Status'] == 2) {
|
||||
throw new Exception('关联云资源查询任务执行失败:' . isset($taskResult['Error']) ? $taskResult['Error']['Message'] : '未知错误');
|
||||
}
|
||||
};
|
||||
if (!$resource_result) {
|
||||
throw new Exception('关联云资源查询任务超时未完成,请稍后重试');
|
||||
}
|
||||
|
||||
$resourceTypes = [];
|
||||
$resourceTypesRegions = [];
|
||||
foreach ($resource_result as $res) {
|
||||
if ($res['ResourceType'] != 'clb') continue;
|
||||
$totalCount = 0;
|
||||
$regions = [];
|
||||
foreach ($res['BindResourceRegionResult'] as $regionRes) {
|
||||
if ($regionRes['TotalCount'] > 0) {
|
||||
$totalCount += $regionRes['TotalCount'];
|
||||
if (!empty($regionRes['Region'])) {
|
||||
$regions[] = $regionRes['Region'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($totalCount > 0) {
|
||||
$resourceTypes[] = $res['ResourceType'];
|
||||
if (!empty($regions)) {
|
||||
$resourceTypesRegions[] = [
|
||||
'ResourceType' => $res['ResourceType'],
|
||||
'Regions' => $regions,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$param = [
|
||||
'OldCertificateId' => $config['cert_id'],
|
||||
'CertificatePublicKey' => $fullchain,
|
||||
'CertificatePrivateKey' => $privatekey,
|
||||
'ResourceTypes' => $resourceTypes,
|
||||
'ResourceTypesRegions' => $resourceTypesRegions,
|
||||
];
|
||||
$retry = 0;
|
||||
while ($retry++ < 10) {
|
||||
try {
|
||||
$data = $this->client->request('UploadUpdateCertificateInstance', $param);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('更新证书内容失败:' . $e->getMessage());
|
||||
}
|
||||
if ($data['DeployStatus'] == 1) {
|
||||
break;
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
$this->log('更新证书内容成功,可能需要一些时间完成各资源的证书更新部署');
|
||||
}
|
||||
|
||||
public function setLogger($func)
|
||||
{
|
||||
$this->logger = $func;
|
||||
|
||||
@@ -31,9 +31,15 @@ class upyun implements DeployInterface
|
||||
$this->login();
|
||||
|
||||
$url = 'https://console.upyun.com/api/https/certificate/';
|
||||
// 如果是 EC 证书,调整私钥头为 EC PRIVATE KEY
|
||||
$privatekey_send = $privatekey;
|
||||
if ($this->isEcCertificate($fullchain)) {
|
||||
$privatekey_send = str_replace('-----BEGIN PRIVATE KEY-----', '-----BEGIN EC PRIVATE KEY-----', $privatekey_send);
|
||||
$privatekey_send = str_replace('-----END PRIVATE KEY-----', '-----END EC PRIVATE KEY-----', $privatekey_send);
|
||||
}
|
||||
$params = [
|
||||
'certificate' => $fullchain,
|
||||
'private_key' => $privatekey,
|
||||
'private_key' => $privatekey_send,
|
||||
];
|
||||
$response = http_request($url, http_build_query($params), null, $this->cookie, null, $this->proxy);
|
||||
$result = json_decode($response['body'], true);
|
||||
@@ -130,4 +136,22 @@ class upyun implements DeployInterface
|
||||
call_user_func($this->logger, $txt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 EC (ECDSA) 证书
|
||||
*/
|
||||
private function isEcCertificate($fullchain)
|
||||
{
|
||||
// 提取第一个证书
|
||||
if (!preg_match('/-----BEGIN CERTIFICATE-----\s*(.+?)\s*-----END CERTIFICATE-----/s', $fullchain, $m)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pubKey = openssl_pkey_get_public($m[0]);
|
||||
if (!$pubKey) return false;
|
||||
|
||||
$details = openssl_pkey_get_details($pubKey);
|
||||
|
||||
return $details && ($details['type'] ?? 0) === OPENSSL_KEYTYPE_EC;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,9 @@ class cloudflare implements DnsInterface
|
||||
$name = $this->domain == $row['name'] ? '@' : str_replace('.'.$this->domain, '', $row['name']);
|
||||
$status = str_ends_with($name, '_pause') ? '0' : '1';
|
||||
$name = $status == '0' ? substr($name, 0, -6) : $name;
|
||||
if ($row['type'] == 'SRV' && isset($row['priority'])) {
|
||||
$row['content'] = $row['priority'] . ' ' . $row['content'];
|
||||
}
|
||||
$list[] = [
|
||||
'RecordId' => $row['id'],
|
||||
'Domain' => $this->domain,
|
||||
@@ -112,6 +115,9 @@ class cloudflare implements DnsInterface
|
||||
$name = $this->domain == $data['result']['name'] ? '@' : str_replace('.' . $this->domain, '', $data['result']['name']);
|
||||
$status = str_ends_with($name, '_pause') ? '0' : '1';
|
||||
$name = $status == '0' ? substr($name, 0, -6) : $name;
|
||||
if ($data['result']['type'] == 'SRV' && isset($data['result']['priority'])) {
|
||||
$data['result']['content'] = $data['result']['priority'] . ' ' . $data['result']['content'];
|
||||
}
|
||||
return [
|
||||
'RecordId' => $data['result']['id'],
|
||||
'Domain' => $this->domain,
|
||||
@@ -257,7 +263,7 @@ class cloudflare implements DnsInterface
|
||||
{
|
||||
$url = $this->baseUrl . $path;
|
||||
|
||||
if (preg_match('/^[0-9a-z]+$/i', $this->ApiKey)) {
|
||||
if (preg_match('/^[0-9a-f]+$/i', $this->ApiKey)) {
|
||||
$headers = [
|
||||
'X-Auth-Email: ' . $this->Email,
|
||||
'X-Auth-Key: ' . $this->ApiKey,
|
||||
|
||||
353
app/lib/dns/spaceship.php
Normal file
353
app/lib/dns/spaceship.php
Normal file
@@ -0,0 +1,353 @@
|
||||
<?php
|
||||
|
||||
namespace app\lib\dns;
|
||||
|
||||
use app\lib\DnsInterface;
|
||||
|
||||
/**
|
||||
* @see https://docs.spaceship.dev/
|
||||
*/
|
||||
class spaceship implements DnsInterface
|
||||
{
|
||||
private $apiKey;
|
||||
private $apiSecret;
|
||||
private $baseUrl = 'https://spaceship.dev/api/v1';
|
||||
private $error;
|
||||
private $domain;
|
||||
private $proxy;
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->apiKey = $config['ak'];
|
||||
$this->apiSecret = $config['sk'];
|
||||
$this->domain = $config['domain'];
|
||||
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function check()
|
||||
{
|
||||
if ($this->getDomainList() != false) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//获取域名列表
|
||||
public function getDomainList($KeyWord = null, $PageNumber = 1, $PageSize = 100)
|
||||
{
|
||||
$param = ['take' => $PageSize, 'skip' => ($PageNumber - 1) * $PageSize];
|
||||
$data = $this->send_reuqest('GET', '/domains', $param);
|
||||
if ($data) {
|
||||
$list = [];
|
||||
foreach ($data['items'] as $row) {
|
||||
$list[] = [
|
||||
'DomainId' => $row['name'],
|
||||
'Domain' => $row['name'],
|
||||
'RecordCount' => 0,
|
||||
];
|
||||
}
|
||||
return ['total' => $data['total'], 'list' => $list];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//获取解析记录列表
|
||||
|
||||
private function send_reuqest($method, $path, $params = null)
|
||||
{
|
||||
$url = $this->baseUrl . $path;
|
||||
|
||||
$headers = [
|
||||
'X-API-Key: ' . $this->apiKey,
|
||||
'X-API-Secret: ' . $this->apiSecret,
|
||||
];
|
||||
|
||||
$body = '';
|
||||
if ($method == 'GET') {
|
||||
if ($params) {
|
||||
$url .= '?' . http_build_query($params);
|
||||
}
|
||||
} else {
|
||||
$body = json_encode($params);
|
||||
$headers[] = 'Content-Type: application/json';
|
||||
}
|
||||
|
||||
$ch = curl_init($url);
|
||||
if ($this->proxy) {
|
||||
curl_set_proxy($ch);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
if ($method == 'POST') {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
} elseif ($method == 'PUT') {
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
} elseif ($method == 'PATCH') {
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
} elseif ($method == 'DELETE') {
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
}
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
|
||||
if ($errno) {
|
||||
$this->setError('Curl error: ' . curl_error($ch));
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
if ($errno) return false;
|
||||
|
||||
$arr = json_decode($response, true);
|
||||
if (!isset($arr['detail'])) {
|
||||
return $arr;
|
||||
} else {
|
||||
$this->setError($response['detail']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//获取子域名解析记录列表
|
||||
|
||||
private function setError($message)
|
||||
{
|
||||
$this->error = $message;
|
||||
//file_put_contents('logs.txt',date('H:i:s').' '.$message."\r\n", FILE_APPEND);
|
||||
}
|
||||
|
||||
//获取解析记录详细信息
|
||||
|
||||
public function getSubDomainRecords($SubDomain, $PageNumber = 1, $PageSize = 20, $Type = null, $Line = null)
|
||||
{
|
||||
if ($SubDomain == '') $SubDomain = '@';
|
||||
return $this->getDomainRecords($PageNumber, $PageSize, null, $SubDomain, null, $Type, $Line);
|
||||
}
|
||||
|
||||
//添加解析记录
|
||||
|
||||
public function getDomainRecords($PageNumber = 1, $PageSize = 20, $KeyWord = null, $SubDomain = null, $Value = null, $Type = null, $Line = null, $Status = null)
|
||||
{
|
||||
$param = ['take' => $PageSize, 'skip' => ($PageNumber - 1) * $PageSize];
|
||||
if (!isNullOrEmpty(($SubDomain))) {
|
||||
$param['host'] = $SubDomain;
|
||||
}
|
||||
$data = $this->send_reuqest('GET', '/dns/records/' . $this->domain, $param);
|
||||
if ($data) {
|
||||
$list = [];
|
||||
foreach ($data['items'] as $row) {
|
||||
$type = $row['type'];
|
||||
$name = $row['name'];
|
||||
if ('MX' == $type) {
|
||||
$address = $row['exchange'];
|
||||
$mx = $row['preference'];
|
||||
} else if ('CNAME' == $type) {
|
||||
$address = $row['cname'];
|
||||
$mx = 0;
|
||||
} else if ('TXT' == $type) {
|
||||
$address = $row['value'];
|
||||
$mx = 0;
|
||||
} else if ('PTR' == $type) {
|
||||
$address = $row['pointer'];
|
||||
$mx = 0;
|
||||
} else if ('NS' == $type) {
|
||||
$address = $row['nameserver'];
|
||||
$mx = 0;
|
||||
} else if ('HTTPS' == $type) {
|
||||
$address = $row['targetName'] . $row['svcParams'] . '|' . $row['svcPriority'];
|
||||
$mx = 0;
|
||||
} else if ('CAA' == $type) {
|
||||
$address = $row['value'];
|
||||
$mx = 0;
|
||||
} else if ('TLSA' == $type) {
|
||||
$address = $row['associationData'];
|
||||
$mx = 0;
|
||||
} else if ('SVRB' == $type) {
|
||||
$address = $row['targetName'] . $row['svcParams'] . '|' . $row['svcPriority'];
|
||||
$mx = 0;
|
||||
} else if ('ALIAS' == $type) {
|
||||
$address = $row['aliasName'];
|
||||
$mx = 0;
|
||||
} else {
|
||||
$address = $row['address'];
|
||||
$mx = 0;
|
||||
}
|
||||
|
||||
$list[] = [
|
||||
'RecordId' => $row['type'] . '|' . $name . '|' . $address . '|' . $mx,
|
||||
'Domain' => $this->domain,
|
||||
'Name' => $row['name'],
|
||||
'Type' => $row['type'],
|
||||
'Value' => $address,
|
||||
'TTL' => $row['ttl'],
|
||||
'Line' => 'default',
|
||||
'MX' => $mx,
|
||||
'Status' => '1',
|
||||
'Weight' => null,
|
||||
'Remark' => null,
|
||||
'UpdateTime' => null,
|
||||
];
|
||||
}
|
||||
return ['total' => $data['total'], 'list' => $list];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//修改解析记录
|
||||
|
||||
public function getDomainRecordInfo($RecordId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//修改解析记录备注
|
||||
|
||||
public function addDomainRecord($Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
|
||||
{
|
||||
$param = [
|
||||
'force' => true,
|
||||
'items' => [
|
||||
[
|
||||
'type' => $this->convertType($Type),
|
||||
'name' => $Name,
|
||||
'address' => $Value,
|
||||
'ttl' => $TTL,
|
||||
]
|
||||
]
|
||||
];
|
||||
$data = $this->send_reuqest('PUT', '/dns/records/' . $this->domain, $param);
|
||||
return !isset($data);
|
||||
}
|
||||
|
||||
//删除解析记录
|
||||
|
||||
private function convertType($type)
|
||||
{
|
||||
return $type;
|
||||
}
|
||||
|
||||
//设置解析记录状态
|
||||
|
||||
public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
|
||||
{
|
||||
$param = [
|
||||
'force' => true,
|
||||
'items' => [
|
||||
[
|
||||
'type' => $this->convertType($Type),
|
||||
'name' => $Name,
|
||||
'address' => $Value,
|
||||
'ttl' => $TTL,
|
||||
]
|
||||
]
|
||||
];
|
||||
$data = $this->send_reuqest('PUT', '/dns/records/' . $this->domain, $param);
|
||||
return !isset($data);
|
||||
}
|
||||
|
||||
//获取解析记录操作日志
|
||||
|
||||
public function updateDomainRecordRemark($RecordId, $Remark)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//获取解析线路列表
|
||||
|
||||
public function deleteDomainRecord($RecordId)
|
||||
{
|
||||
$array = explode("|", $RecordId);
|
||||
$type = $array[0];
|
||||
$name = $array[1];
|
||||
$address = $array[2];
|
||||
$mx = $array[3];
|
||||
if ('MX' == $type) {
|
||||
$param = [
|
||||
[
|
||||
'type' => $type,
|
||||
'name' => $name,
|
||||
'exchange' => $address,
|
||||
'preference' => (int)$mx,
|
||||
]
|
||||
];
|
||||
} else if ('TXT' == $type) {
|
||||
$param = [
|
||||
[
|
||||
'type' => $type,
|
||||
'name' => $name,
|
||||
'value' => $address,
|
||||
]
|
||||
];
|
||||
} else if ('CNAME' == $type) {
|
||||
$param = [
|
||||
[
|
||||
'type' => $type,
|
||||
'name' => $name,
|
||||
'cname' => $address,
|
||||
]
|
||||
];
|
||||
} else if ('ALIAS' == $type) {
|
||||
$param = [
|
||||
[
|
||||
'type' => $type,
|
||||
'name' => $name,
|
||||
'aliasName' => $address,
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$param = [
|
||||
[
|
||||
'type' => $type,
|
||||
'name' => $name,
|
||||
'address' => $address,
|
||||
]
|
||||
];
|
||||
}
|
||||
$data = $this->send_reuqest('DELETE', '/dns/records/' . $this->domain, $param);
|
||||
return !isset($data);
|
||||
}
|
||||
|
||||
//获取域名信息
|
||||
|
||||
public function setDomainRecordStatus($RecordId, $Status)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//获取域名最低TTL
|
||||
|
||||
public function getDomainRecordLog($PageNumber = 1, $PageSize = 20, $KeyWord = null, $StartDate = null, $endDate = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getRecordLine()
|
||||
{
|
||||
return ['default' => ['name' => '默认', 'parent' => null]];
|
||||
}
|
||||
|
||||
public function getDomainInfo()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMinTTL()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function addDomain($Domain)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class ExpireNoticeService
|
||||
|
||||
private function refreshDomainList()
|
||||
{
|
||||
$domainList = Db::name('domain')->field('id,name')->where('expiretime', null)->where('checkstatus', 0)->select();
|
||||
$domainList = Db::name('domain')->field('id,name')->where('checkstatus', 0)->select();
|
||||
$count = 0;
|
||||
foreach ($domainList as $domain) {
|
||||
$res = $this->updateDomainDate($domain['id'], $domain['name']);
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
{block name="script"}
|
||||
<script src="/static/js/vue-2.7.16.min.js"></script>
|
||||
<script src="/static/js/layer/layer.js"></script>
|
||||
<script src="/static/js/bootstrapValidator.min.js"></script>
|
||||
<script src="/static/js/bootstrapValidator.min.js?v=2"></script>
|
||||
<script>
|
||||
var action = '{$action}';
|
||||
var info = {$info|json_encode|raw};
|
||||
|
||||
@@ -139,12 +139,15 @@
|
||||
<div class="form-group">
|
||||
<select name="status" class="form-control"><option value="">所有状态</option><option value="1">即将到期</option><option value="2">已到期</option></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select name="order" class="form-control"><option value="">默认排序</option><option value="1">注册时间↑</option><option value="2">注册时间↓</option><option value="3">到期时间↑</option><option value="4">到期时间↓</option></select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><i class="fa fa-search"></i> 搜索</button>
|
||||
<a href="javascript:searchClear()" class="btn btn-default" title="刷新域名列表"><i class="fa fa-refresh"></i> 刷新</a>
|
||||
{if request()->user['level'] eq 2}<a href="javascript:addframe()" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">批量操作 <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu"><li><a href="/domain/add">添加域名</a></li><li><a href="javascript:operation('editremark')">修改域名备注</a></li><li><a href="javascript:operation('opennotice')">开启到期提醒</a></li><li><a href="javascript:operation('closenotice')">关闭到期提醒</a></li><li><a href="javascript:operation('delete')">删除域名</a></li><li role="separator" class="divider"></li><li><a href="javascript:operation('addrecord')">添加解析</a></li><li><a href="javascript:operation('editrecord')">修改解析</a></li></ul>
|
||||
<ul class="dropdown-menu"><li><a href="/domain/add">添加域名</a></li><li><a href="javascript:operation('editremark')">修改域名备注</a></li><li><a href="javascript:operation('opennotice')">开启到期提醒</a></li><li><a href="javascript:operation('closenotice')">关闭到期提醒</a></li><li><a href="javascript:operation('updateexpire')">刷新到期时间</a></li><li><a href="javascript:operation('delete')">删除域名</a></li><li role="separator" class="divider"></li><li><a href="javascript:operation('addrecord')">添加解析</a></li><li><a href="javascript:operation('editrecord')">修改解析</a></li></ul>
|
||||
</div>
|
||||
<a href="/domain/expirenotice" class="btn btn-default">到期提醒设置</a>{/if}
|
||||
</form>
|
||||
@@ -535,6 +538,30 @@ function operation(action){
|
||||
}, function(){
|
||||
layer.close(confirmobj);
|
||||
});
|
||||
}else if(action == 'updateexpire'){
|
||||
var confirmobj = layer.confirm('提交后将异步刷新所选域名的到期时间', {
|
||||
btn: ['确定','取消']
|
||||
}, function(){
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/domain/op/act/updateexpire',
|
||||
data : {ids: ids},
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.closeAll();
|
||||
layer.alert(data.msg, {icon: 1});
|
||||
searchRefresh();
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2});
|
||||
}
|
||||
}
|
||||
});
|
||||
}, function(){
|
||||
layer.close(confirmobj);
|
||||
});
|
||||
}else{
|
||||
var is_notice = action == 'opennotice' ? 1 : 0;
|
||||
var ii = layer.load(2);
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<div class="form-group" v-show="set.switchtype==0">
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>记录值</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="value" v-model="set.value" placeholder="支持填写IPv4或CNAME地址" class="form-control" required>
|
||||
<input type="text" name="value" v-model="set.value" placeholder="支持填写IP或CNAME地址" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-show="set.switchtype==0&&dnstype=='cloudflare'">
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"cccyun/php-whois": "^1.0",
|
||||
"cccyun/think-captcha": "^3.0",
|
||||
"guzzlehttp/guzzle": "^7.0",
|
||||
"phpmailer/phpmailer": "^6.10",
|
||||
"phpmailer/phpmailer": "^7.0",
|
||||
"symfony/polyfill-intl-idn": "^1.32",
|
||||
"symfony/polyfill-mbstring": "^1.32",
|
||||
"symfony/polyfill-php81": "^1.32",
|
||||
|
||||
48
composer.lock
generated
48
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f7c4abfaf4cb80cd99107e9e1763e75c",
|
||||
"content-hash": "34b2ff614d9cf3cc515823086a4f091b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "cccyun/php-whois",
|
||||
@@ -445,16 +445,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpmailer/phpmailer",
|
||||
"version": "v6.11.1",
|
||||
"version": "v7.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||
"reference": "d9e3b36b47f04b497a0164c5a20f92acb4593284"
|
||||
"reference": "360ae911ce62e25e11249f6140fa58939f556ebe"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/d9e3b36b47f04b497a0164c5a20f92acb4593284",
|
||||
"reference": "d9e3b36b47f04b497a0164c5a20f92acb4593284",
|
||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/360ae911ce62e25e11249f6140fa58939f556ebe",
|
||||
"reference": "360ae911ce62e25e11249f6140fa58939f556ebe",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -468,13 +468,13 @@
|
||||
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
||||
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
||||
"phpcompatibility/php-compatibility": "^9.3.5",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"squizlabs/php_codesniffer": "^3.7.2",
|
||||
"phpcompatibility/php-compatibility": "^10.0.0@dev",
|
||||
"squizlabs/php_codesniffer": "^3.13.5",
|
||||
"yoast/phpunit-polyfills": "^1.0.4"
|
||||
},
|
||||
"suggest": {
|
||||
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
||||
"directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example",
|
||||
"ext-imap": "Needed to support advanced email address parsing according to RFC822",
|
||||
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
||||
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
||||
@@ -515,7 +515,7 @@
|
||||
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
||||
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.11.1"
|
||||
"source": "https://github.com/PHPMailer/PHPMailer/tree/v7.0.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -523,7 +523,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-30T11:54:53+00:00"
|
||||
"time": "2025-11-25T07:18:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -1452,16 +1452,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "d4f4a66866fe2451f61296924767280ab5732d9d"
|
||||
"reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d",
|
||||
"reference": "d4f4a66866fe2451f61296924767280ab5732d9d",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc",
|
||||
"reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1504,7 +1504,7 @@
|
||||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/yaml/tree/v7.3.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1524,7 +1524,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-27T11:34:33+00:00"
|
||||
"time": "2025-09-27T09:00:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "topthink/framework",
|
||||
@@ -1907,16 +1907,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v7.3.4",
|
||||
"version": "v7.3.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb"
|
||||
"reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
|
||||
"reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d",
|
||||
"reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1970,7 +1970,7 @@
|
||||
"dump"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v7.3.4"
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v7.3.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1990,7 +1990,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-11T10:12:26+00:00"
|
||||
"time": "2025-09-27T09:00:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "topthink/think-trace",
|
||||
@@ -2046,7 +2046,7 @@
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {},
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
@@ -2060,6 +2060,6 @@
|
||||
"ext-sockets": "*",
|
||||
"ext-ssh2": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ return [
|
||||
'show_error_msg' => true,
|
||||
'exception_tmpl' => \think\facade\App::getAppPath() . 'view/exception.tpl',
|
||||
|
||||
'version' => '1042',
|
||||
'version' => '1044',
|
||||
|
||||
'dbversion' => '1040'
|
||||
];
|
||||
|
||||
BIN
public/static/images/spaceship.ico
Normal file
BIN
public/static/images/spaceship.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
2
public/static/js/bootstrapValidator.min.js
vendored
2
public/static/js/bootstrapValidator.min.js
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user