46 Commits
2.0.0 ... 2.6.0

Author SHA1 Message Date
net909
079a142b40 新增whois查询域名到期时间+到期提醒 2025-04-28 20:11:33 +08:00
net909
76e9adb405 支持添加的子级域名申请SSL 2025-04-26 20:19:28 +08:00
net909
1723761b94 修改提示 2025-04-07 21:38:47 +08:00
net909
9fb3764878 多个域名批量添加解析、修改解析 2025-04-07 21:26:38 +08:00
net909
651132967f 支持阿里云解析权重修改 2025-04-05 23:03:20 +08:00
net909
81a85fce45 修复部分DNS记录搜索 CF支持暂停解析 2025-04-01 09:47:26 +08:00
net909
4c62084bc6 readme 2025-03-23 12:11:49 +08:00
net909
33ba9a6ebd fix 2025-03-23 11:39:03 +08:00
net909
64d0585788 证书可批量删除、新增支持京东云DNS 2025-03-22 22:28:42 +08:00
net909
1354f63050 version 2025-03-08 09:32:49 +08:00
net909
646fa54bfa fix 2025-03-08 09:32:17 +08:00
net909
2023fb9808 fix 2025-03-07 18:04:34 +08:00
net909
6a45222c1f 支持又拍云部署 2025-03-07 18:04:02 +08:00
net909
44790639cd version 2025-03-05 17:28:51 +08:00
net909
5b12a368fc 新增PowerDNS对接,优化操作日志 2025-03-04 20:49:40 +08:00
net909
36622e6642 新增PVE部署 2025-03-02 14:59:02 +08:00
net909
12d8017df5 增加群辉面板部署,支持状态过滤 2025-02-28 23:04:18 +08:00
消失的彩虹海
70f2e0d487 Merge pull request #150 from devhaozi/main
支持括彩云部署
2025-02-24 22:37:04 +08:00
net909
521275ee33 修复申请中文域名证书,支持批量删域名 2025-02-24 22:34:54 +08:00
耗子
c06bf2d34c 优化括彩云文案 2025-02-24 19:44:40 +08:00
耗子
39dc789ac3 支持括彩云部署 2025-02-24 19:40:37 +08:00
耗子
0877674efb 支持括彩云部署 2025-02-24 19:03:06 +08:00
net909
fe9a50469d 支持宝塔邮局部署 2025-02-13 14:46:49 +08:00
net909
d9f8cc18eb 增加tgbot反代url 2025-02-05 11:48:08 +08:00
net909
48d5ad7569 修改CF优选IP添加解析 2025-02-05 11:17:26 +08:00
net909
8980910d47 增加上次运行时间显示 2025-02-01 10:54:47 +08:00
net909
2ed8a717db 修复namesilo添加解析 2025-02-01 10:46:39 +08:00
net909
b4258dbc81 修复CF解析列表 2025-01-30 10:59:41 +08:00
net909
d1eb6267a2 支持火山引擎更多产品部署 2025-01-24 14:36:29 +08:00
net909
2c81b36249 增加导入已有证书,支持天翼云CDN部署 2025-01-10 14:54:45 +08:00
net909
8d5a9bc083 version 2025-01-05 10:57:43 +08:00
net909
31300d8a7b 优化SSL订单等待耗时 2025-01-05 10:56:02 +08:00
net909
300f2a9b92 修复部分接口请求异常 2024-12-31 15:30:59 +08:00
net909
865275c065 新增西部数码虚拟主机部署 2024-12-30 18:00:47 +08:00
net909
cae9887e05 SSL订单提示优化 2024-12-28 17:17:14 +08:00
net909
6de361171e readme 2024-12-28 17:02:59 +08:00
net909
e59ff8997e 支持批量添加域名 2024-12-28 16:59:48 +08:00
net909
6ce2e006b5 修复泛域名CNAME代理 2024-12-27 22:27:38 +08:00
net909
197d816bbb 修复Goedge账号添加 2024-12-27 13:40:20 +08:00
net909
dfded1122b 云服务商接口支持代理请求 2024-12-26 14:34:55 +08:00
net909
8201318bd7 支持MW面板部署,增加群机器人webhook通知 2024-12-25 20:08:59 +08:00
net909
b31aee7166 fix 2024-12-25 11:37:43 +08:00
net909
06b43fa33f 修复华为云添加TXT解析 2024-12-24 20:41:46 +08:00
net909
fa9235562a 修复华为云部署 2024-12-23 22:31:26 +08:00
net909
92ba34833b 修复1panel 2024-12-22 16:54:34 +08:00
net909
83c1afc186 fix 2024-12-22 11:09:54 +08:00
112 changed files with 6047 additions and 937 deletions

118
README.md
View File

@@ -1,57 +1,31 @@
## 聚合DNS管理系统
# 彩虹聚合DNS管理系统
聚合DNS管理系统可以实现在一个网站内管理多个平台的域名解析目前已支持的域名平台有
<div align="center">
- 阿里云
- 腾讯云
- 华为云
- 西部数码
- DNSLA
- CloudFlare
[![GitHub stars](https://img.shields.io/github/stars/netcccyun/dnsmgr?style=flat)](https://github.com/netcccyun/dnsmgr/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/netcccyun/dnsmgr?style=flat)](https://github.com/netcccyun/dnsmgr/forks)
[![Docker Pulls](https://img.shields.io/docker/pulls/netcccyun/dnsmgr?style=flat)](https://hub.docker.com/r/netcccyun/dnsmgr)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/netcccyun/dnsmgr)](https://github.com/netcccyun/dnsmgr/releases)
[![GitHub last commit](https://img.shields.io/github/last-commit/netcccyun/dnsmgr)](https://github.com/netcccyun/dnsmgr/commits/main)
### 功能特性
</div>
- 多用户管理,可为每个用户可分配不同的域名解析权限
- 提供API接口可获取域名单独的登录链接方便各种IDC系统对接
- 容灾切换功能支持ping、tcp、http(s)检测协议并自动暂停/修改域名解析,并支持邮件、微信公众号通知
- CF优选IP功能支持获取最新的Cloudflare优选IP并自动更新到解析记录
- SSL证书申请与自动部署功能支持从Let's Encrypt等渠道申请SSL证书并自动部署到各种面板、云服务商、服务器等
彩虹聚合DNS管理系统 是一款基于ThinkPHP开发的网站程序可实现在单一网站内管理多个平台的域名解析目前已支持的域名解析平台有阿里云、腾讯云、华为云、百度云、西部数码、火山引擎、DNSLA、CloudFlare、Namesilo、PowerDNS
### 演示截图
## 功能特性
添加域名账户
- 多用户管理,可为每个用户可分配不同的域名解析权限;
- 提供API接口可获取域名单独的登录链接方便各种IDC系统对接
- 容灾切换功能支持ping、tcp、http(s)检测协议并自动暂停/修改域名解析,并支持发送通知;
- CF优选IP功能支持获取最新的Cloudflare优选IP并自动更新到解析记录
- SSL证书申请与自动部署功能支持从Let's Encrypt等渠道申请SSL证书并自动部署到各种面板、云服务商、服务器等
- 支持邮件、微信公众号、Telegram、钉钉、飞书、企业微信等多种通知渠道。
![](https://p0.meituan.net/csc/090508cdc7aaabd185ba9c76a8c099f9283946.png)
## 部署方式
域名管理列表
### 自部署
![](https://p0.meituan.net/csc/60bf3f607d40f30f152ad1f6ee3be098357839.png)
域名DNS解析管理支持解析批量操作
![](https://p0.meituan.net/csc/f99c599d4febced404c88672dd50d62c212895.png)
用户管理添加用户支持为用户开启API接口
![](https://p0.meituan.net/csc/d1bd90bedca9b6cbc5da40286bdb5cd5228438.png)
CF优选IP功能添加优选IP任务
![](https://p1.meituan.net/csc/da70c76753aee4bce044d16fadd56e5f217660.png)
SSL证书申请功能
![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154857.png)
![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154652.png?a)
SSL证书自动部署功能
![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154702.png)
![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154804.png)
### 部署方法
可以使用宝塔、Kangle等任意支持PHP-MySQL的环境部署
* 从[Release](https://github.com/netcccyun/dnsmgr/releases)页面下载安装包
@@ -71,6 +45,8 @@ SSL证书自动部署功能
* 访问首页登录控制面板
* 后续更新方式:重新下载安装包上传覆盖即可
##### 伪静态规则
* Nginx
@@ -96,7 +72,7 @@ location / {
</IfModule>
```
### Docker部署方法
### Docker 部署
首先需要安装Docker然后执行以下命令拉取镜像并启动启动后监听8081端口
@@ -110,7 +86,13 @@ docker run --name dnsmgr -dit -p 8081:80 -v /var/dnsmgr:/app/www netcccyun/dnsmg
docker restart dnsmgr
```
### docker-compose部署方法
从国内镜像地址拉取:
```
docker pull swr.cn-east-3.myhuaweicloud.com/netcccyun/dnsmgr:latest
```
### docker-compose 部署
```
version: '3'
@@ -151,6 +133,7 @@ networks:
```
在运行之前请创建好目录
```
mkdir -p ./web
mkdir -p ./mysql/conf
@@ -163,6 +146,7 @@ sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_
```
登陆mysql容器创建数据库
```
docker exec -it dnsmgr-mysql /bin/bash
mysql -uroot -p123456
@@ -171,9 +155,45 @@ create database dnsmgr;
在install界面链接IP填写dnsmgr-mysql
### 作者信息
## 演示截图
消失的彩虹海(https://blog.cccyun.cn)
添加域名账户
![](https://p0.meituan.net/csc/090508cdc7aaabd185ba9c76a8c099f9283946.png)
域名管理列表
![](https://p0.meituan.net/csc/60bf3f607d40f30f152ad1f6ee3be098357839.png)
域名DNS解析管理支持解析批量操作
![](https://p0.meituan.net/csc/f99c599d4febced404c88672dd50d62c212895.png)
用户管理添加用户支持为用户开启API接口
![](https://p0.meituan.net/csc/d1bd90bedca9b6cbc5da40286bdb5cd5228438.png)
CF优选IP功能添加优选IP任务
![](https://p1.meituan.net/csc/da70c76753aee4bce044d16fadd56e5f217660.png)
SSL证书申请功能
![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154857.png)
![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154652.png?a)
SSL证书自动部署功能
![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154702.png)
![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154804.png)
## 支持与反馈
🌐 作者信息:消失的彩虹海(https://blog.cccyun.cn)
⭐ 如果您觉得本项目对您有帮助,欢迎给项目点个 Star
### 其他推荐

View File

@@ -1,12 +1,14 @@
<?php
// 应用公共文件
use think\facade\Db;
use think\facade\Config;
use think\facade\Request;
function get_curl($url, $post = 0, $referer = 0, $cookie = 0, $header = 0, $ua = 0, $nobody = 0, $addheader = 0)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$httpheader[] = "Accept: */*";
@@ -165,6 +167,11 @@ function getSubstr($str, $leftStr, $rightStr)
}
}
function arrays_are_equal($array1, $array2)
{
return empty(array_diff($array1, $array2)) && empty(array_diff($array2, $array1));
}
function checkRefererHost()
{
if (!Request::header('referer')) {
@@ -288,6 +295,16 @@ function convert_second($s)
function getMainDomain($host)
{
if (filter_var($host, FILTER_VALIDATE_IP)) return $host;
$domains = config('temp.domains');
if (!$domains) {
$domains = Db::name('domain')->column('name');
config(['domains'=>$domains], 'temp');
}
foreach ($domains as $domain) {
if (str_ends_with($host, $domain)) {
return $domain;
}
}
$domain_root = file_get_contents(app()->getBasePath() . 'data' . DIRECTORY_SEPARATOR . 'domain_root.txt');
$domain_root = explode("\r\n", $domain_root);
$data = explode('.', $host);
@@ -407,26 +424,7 @@ function curl_client($url, $data = null, $referer = null, $cookie = null, $heade
}
if ($proxy) {
$proxy_server = config_get('proxy_server');
$proxy_port = intval(config_get('proxy_port'));
$proxy_userpwd = config_get('proxy_user') . ':' . config_get('proxy_pwd');
$proxy_type = config_get('proxy_type');
if ($proxy_type == 'https') {
$proxy_type = CURLPROXY_HTTPS;
} elseif ($proxy_type == 'sock4') {
$proxy_type = CURLPROXY_SOCKS4;
} elseif ($proxy_type == 'sock5') {
$proxy_type = CURLPROXY_SOCKS5;
} else {
$proxy_type = CURLPROXY_HTTP;
}
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_PROXY, $proxy_server);
curl_setopt($ch, CURLOPT_PROXYPORT, $proxy_port);
if ($proxy_userpwd != ':') {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_userpwd);
}
curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type);
curl_set_proxy($ch);
}
$ret = curl_exec($ch);
@@ -442,4 +440,65 @@ function curl_client($url, $data = null, $referer = null, $cookie = null, $heade
$header = substr($ret, 0, $headerSize);
$body = substr($ret, $headerSize);
return ['code' => $httpCode, 'redirect_url' => $redirect_url, 'header' => $header, 'body' => $body];
}
}
function curl_set_proxy(&$ch)
{
$proxy_server = config_get('proxy_server');
$proxy_port = intval(config_get('proxy_port'));
$proxy_userpwd = config_get('proxy_user') . ':' . config_get('proxy_pwd');
$proxy_type = config_get('proxy_type');
if (empty($proxy_server) || empty($proxy_port)) {
return;
}
if ($proxy_type == 'https') {
$proxy_type = CURLPROXY_HTTPS;
} elseif ($proxy_type == 'sock4') {
$proxy_type = CURLPROXY_SOCKS4;
} elseif ($proxy_type == 'sock5') {
$proxy_type = CURLPROXY_SOCKS5;
} else {
$proxy_type = CURLPROXY_HTTP;
}
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_PROXY, $proxy_server);
curl_setopt($ch, CURLOPT_PROXYPORT, $proxy_port);
if ($proxy_userpwd != ':') {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_userpwd);
}
curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type);
}
function convertDomainToAscii($domain) {
if (preg_match('/[\x{4e00}-\x{9fa5}]/u', $domain)) {
return idn_to_ascii($domain);
} else {
return $domain;
}
}
function convertDomainToUtf8($domain) {
if (preg_match('/^xn--/', $domain)) {
return idn_to_utf8($domain);
} else {
return $domain;
}
}
function getDomainDate($domain)
{
try {
$whois = \Iodev\Whois\Factory::get()->createWhois();
$info = $whois->loadDomainInfo($domain);
if ($info) {
if ($info->expirationDate > 0) {
return [date('Y-m-d H:i:s', $info->creationDate), date('Y-m-d H:i:s', $info->expirationDate)];
} else {
throw new Exception('域名到期时间未知');
}
} else {
throw new Exception('域名信息未找到');
}
} catch (Exception $e) {
throw new Exception('查询域名whois失败: ' . $e->getMessage());
}
}

View File

@@ -38,7 +38,7 @@ class Auth extends BaseController
$user = Db::name('user')->where('username', $username)->find();
if ($user && password_verify($password, $user['password'])) {
if ($user['status'] == 0) return json(['code' => -1, 'msg' => '此用户已被封禁', 'vcode' => 1]);
if ($user['totp_open'] == 1 && !empty($user['totp_secret'])) {
if (isset($user['totp_open']) && $user['totp_open'] == 1 && !empty($user['totp_secret'])) {
session('pre_login_user', $user['id']);
if (file_exists($login_limit_file)) {
unlink($login_limit_file);
@@ -53,7 +53,9 @@ class Auth extends BaseController
} else {
if ($user) {
Db::name('log')->insert(['uid' => $user['id'], 'action' => '登录失败', 'data' => 'IP:' . $this->clientip, 'addtime' => date("Y-m-d H:i:s")]);
if ($user['totp_open'] == 1 && !empty($user['totp_secret'])) $login_limit_count = 10;
if (isset($user['totp_open']) && $user['totp_open'] == 1 && !empty($user['totp_secret'])) {
return json(['code' => -1, 'msg' => '用户名或密码错误', 'vcode' => 1]);
}
}
if (!file_exists($login_limit_file)) {
$login_limit = ['count' => 0, 'time' => 0];

View File

@@ -36,7 +36,7 @@ class Cert extends BaseController
$select = Db::name('cert_account')->where('deploy', $deploy);
if (!empty($kw)) {
$select->whereLike('name|remark', '%' . $kw . '%');
$select->whereLike('name|remark', '%' . $kw . '%')->whereOr('id', $kw);
}
$total = $select->count();
$rows = $select->order('id', 'desc')->limit($offset, $limit)->select();
@@ -203,6 +203,7 @@ class Cert extends BaseController
$domain = $this->request->post('domain', null, 'trim');
$id = input('post.id');
$type = input('post.type', null, 'trim');
$status = input('post.status', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
@@ -216,6 +217,17 @@ class Cert extends BaseController
if (!empty($type)) {
$select->where('B.type', $type);
}
if (!isNullOrEmpty($status)) {
if ($status == '5') {
$select->where('A.status', '<', 0);
} elseif ($status == '6') {
$select->where('A.expiretime', '<', date('Y-m-d H:i:s', time() + 86400 * 7))->where('A.expiretime', '>=', date('Y-m-d H:i:s'));
} elseif ($status == '7') {
$select->where('A.expiretime', '<', date('Y-m-d H:i:s'));
} else {
$select->where('A.status', $status);
}
}
$total = $select->count();
$rows = $select->fieldRaw('A.*,B.type,B.remark aremark')->order('id', 'desc')->limit($offset, $limit)->select();
@@ -272,7 +284,7 @@ class Cert extends BaseController
foreach($domains as $domain){
$domainList[] = [
'oid' => $id,
'domain' => $domain,
'domain' => convertDomainToAscii($domain),
'sort' => $i++,
];
}
@@ -310,13 +322,79 @@ class Cert extends BaseController
foreach($domains as $domain){
$domainList[] = [
'oid' => $id,
'domain' => $domain,
'domain' => convertDomainToAscii($domain),
'sort' => $i++,
];
}
Db::name('cert_domain')->insertAll($domainList);
Db::commit();
return json(['code' => 0, 'msg' => '修改证书订单成功!']);
} elseif ($action == 'import') {
$fullchain = input('post.fullchain', null, 'trim');
$privatekey = input('post.privatekey', null, 'trim');
if (!openssl_x509_read($fullchain)) return json(['code' => -1, 'msg' => '证书内容填写错误']);
if (!openssl_get_privatekey($privatekey)) return json(['code' => -1, 'msg' => '私钥内容填写错误']);
if (!openssl_x509_check_private_key($fullchain, $privatekey)) return json(['code' => -1, 'msg' => 'SSL证书与私钥不匹配']);
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo || !isset($certInfo['extensions']['subjectAltName'])) return json(['code' => -1, 'msg' => '证书内容解析失败']);
$domains = [];
$subjectAltName = explode(',', $certInfo['extensions']['subjectAltName']);
foreach ($subjectAltName as $domain) {
$domain = trim($domain);
if (strpos($domain, 'DNS:') === 0) $domain = substr($domain, 4);
if (!empty($domain)) {
$domains[] = $domain;
}
}
$domains = array_unique($domains);
if (empty($domains)) return json(['code' => -1, 'msg' => '证书绑定域名不能为空']);
$issuetime = date('Y-m-d H:i:s', $certInfo['validFrom_time_t']);
$expiretime = date('Y-m-d H:i:s', $certInfo['validTo_time_t']);
$issuer = $certInfo['issuer']['CN'];
$order_ids = Db::name('cert_order')->where('issuetime', $issuetime)->column('id');
if (!empty($order_ids)) {
foreach ($order_ids as $order_id) {
$domains2 = Db::name('cert_domain')->where('oid', $order_id)->column('domain');
if (arrays_are_equal($domains2, $domains)) {
return json(['code' => -1, 'msg' => '该证书已存在,无需重复添加']);
}
}
}
$order = [
'aid' => input('post.aid/d'),
'keytype' => input('post.keytype'),
'keysize' => input('post.keysize'),
'addtime' => date('Y-m-d H:i:s'),
'updatetime' => date('Y-m-d H:i:s'),
'issuetime' => $issuetime,
'expiretime' => $expiretime,
'issuer' => $issuer,
'status' => 3,
'fullchain' => $fullchain,
'privatekey' => $privatekey,
];
if (empty($order['aid']) || empty($order['keytype']) || empty($order['keysize'])) return json(['code' => -1, 'msg' => '必填参数不能为空']);
$res = $this->check_order($order, $domains);
if (is_array($res)) return json($res);
Db::startTrans();
$id = Db::name('cert_order')->insertGetId($order);
$domainList = [];
$i = 1;
foreach ($domains as $domain) {
$domainList[] = [
'oid' => $id,
'domain' => $domain,
'sort' => $i++,
];
}
Db::name('cert_domain')->insertAll($domainList);
Db::commit();
return json(['code' => 0, 'msg' => '导入证书成功!']);
} elseif ($action == 'del') {
$id = input('post.id/d');
$dcount = DB::name('cert_deploy')->where('oid', $id)->count();
@@ -357,6 +435,35 @@ class Cert extends BaseController
$file = app()->getRuntimePath().'log/'.$processid.'.log';
if(!file_exists($file)) return json(['code' => -1, 'msg' => '日志文件不存在']);
return json(['code' => 0, 'data' => file_get_contents($file), 'time'=>filemtime($file)]);
} elseif ($action == 'operation') {
$ids = input('post.ids');
$success = 0;
foreach ($ids as $id) {
if (input('post.action') == 'delete') {
$dcount = DB::name('cert_deploy')->where('oid', $id)->count();
if ($dcount > 0) continue;
try {
(new CertOrderService($id))->cancel();
} catch (Exception $e) {
}
Db::name('cert_order')->where('id', $id)->delete();
Db::name('cert_domain')->where('oid', $id)->delete();
$success++;
} elseif (input('post.action') == 'reset') {
try {
$service = new CertOrderService($id);
$service->cancel();
$service->reset();
$success++;
} catch (Exception $e) {
}
} elseif (input('post.action') == 'open' || input('post.action') == 'close') {
$isauto = input('post.action') == 'open' ? 1 : 0;
Db::name('cert_order')->where('id', $id)->update(['isauto' => $isauto]);
$success++;
}
}
return json(['code' => 0, 'msg' => '成功操作' . $success . '个证书订单']);
}
return json(['code' => -3]);
}
@@ -368,13 +475,18 @@ class Cert extends BaseController
$max_domains = CertHelper::$cert_config[$account['type']]['max_domains'];
$wildcard = CertHelper::$cert_config[$account['type']]['wildcard'];
$cname = CertHelper::$cert_config[$account['type']]['cname'];
if (count($domains) > $max_domains) return ['code' => -1, 'msg' => '域名数量不能超过'.$max_domains.'个'];
if (count($domains) > $max_domains) {
if (!(count($domains) == 2 && $max_domains == 1 && ltrim($domains[0], 'www.') == ltrim($domains[1], 'www.'))) {
return ['code' => -1, 'msg' => '域名数量不能超过'.$max_domains.'个'];
}
}
foreach($domains as $domain){
if(!$wildcard && strpos($domain, '*') !== false) return ['code' => -1, 'msg' => '该证书账户类型不支持泛域名'];
if (!$wildcard && strpos($domain, '*') !== false) return ['code' => -1, 'msg' => '该证书账户类型不支持泛域名'];
$mainDomain = getMainDomain($domain);
$drow = Db::name('domain')->where('name', $mainDomain)->find();
if (!$drow) {
if (substr($domain, 0, 2) == '*.') $domain = substr($domain, 2);
if (!$cname || !Db::name('cert_cname')->where('domain', $domain)->where('status', 1)->find()) {
return ['code' => -1, 'msg' => '域名'.$domain.'未在本系统添加'];
}
@@ -406,7 +518,7 @@ class Cert extends BaseController
return json(['code' => 0, 'msg' => '添加DNS记录成功请等待DNS生效后点击验证']);
}
}catch(Exception $e){
return json(['code' => -1, 'msg' => $e->getMessage()]);
return json(['code' => -1, 'msg' => $e->getMessage(), 'trace' => $e->getTrace()]);
}
}
@@ -437,6 +549,20 @@ class Cert extends BaseController
return View::fetch();
}
public function order_import()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
$accounts = [];
foreach (Db::name('cert_account')->where('deploy', 0)->select() as $row) {
$accounts[$row['id']] = ['name'=>$row['id'].'_'.CertHelper::$cert_config[$row['type']]['name'], 'type'=>$row['type']];
if (!empty($row['remark'])) {
$accounts[$row['id']]['name'] .= '' . $row['remark'] . '';
}
}
View::assign('accounts', $accounts);
return View::fetch();
}
public function deploytask()
{
@@ -455,6 +581,7 @@ class Cert extends BaseController
$domain = $this->request->post('domain', null, 'trim');
$oid = input('post.oid');
$type = input('post.type', null, 'trim');
$status = input('post.status', null, 'trim');
$remark = input('post.remark', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
@@ -469,6 +596,9 @@ class Cert extends BaseController
if (!empty($type)) {
$select->where('B.type', $type);
}
if (!isNullOrEmpty($status)) {
$select->where('A.status', $status);
}
if (!empty($remark)) {
$select->where('A.remark', $remark);
}
@@ -543,6 +673,27 @@ class Cert extends BaseController
$file = app()->getRuntimePath().'log/'.$processid.'.log';
if(!file_exists($file)) return json(['code' => -1, 'msg' => '日志文件不存在']);
return json(['code' => 0, 'data' => file_get_contents($file), 'time'=>filemtime($file)]);
} elseif ($action == 'operation') {
$ids = input('post.ids');
$success = 0;
foreach ($ids as $id) {
if (input('post.action') == 'delete') {
Db::name('cert_deploy')->where('id', $id)->delete();
$success++;
} elseif (input('post.action') == 'reset') {
try {
$service = new CertDeployService($id);
$service->reset();
$success++;
} catch (Exception $e) {
}
} elseif (input('post.action') == 'open' || input('post.action') == 'close') {
$active = input('post.action') == 'open' ? 1 : 0;
Db::name('cert_deploy')->where('id', $id)->update(['active' => $active]);
$success++;
}
}
return json(['code' => 0, 'msg' => '成功操作' . $success . '个任务']);
}
return json(['code' => -3]);
}
@@ -566,7 +717,7 @@ class Cert extends BaseController
$service->process(true);
return json(['code' => 0, 'msg' => 'SSL证书部署任务执行成功']);
}catch(Exception $e){
return json(['code' => -1, 'msg' => $e->getMessage()]);
return json(['code' => -1, 'msg' => $e->getMessage(), 'trace' => $e->getTrace()]);
}
}

View File

@@ -39,6 +39,7 @@ class Dmonitor extends BaseController
{
if (!checkPermission(2)) return json(['total' => 0, 'rows' => []]);
$type = input('post.type/d', 1);
$status = input('post.status', null);
$kw = input('post.kw', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
@@ -57,6 +58,9 @@ class Dmonitor extends BaseController
$select->whereLike('remark', '%' . $kw . '%');
}
}
if (!isNullOrEmpty($status)) {
$select->where('status', intval($status));
}
$total = $select->count();
$list = $select->order('A.id', 'desc')->limit($offset, $limit)->field('A.*,B.name domain')->select()->toArray();
@@ -87,6 +91,7 @@ class Dmonitor extends BaseController
'cycle' => input('post.cycle/d'),
'timeout' => input('post.timeout/d'),
'proxy' => input('post.proxy/d'),
'cdn' => input('post.cdn') == 'true' || input('post.cdn') == '1' ? 1 : 0,
'remark' => input('post.remark', null, 'trim'),
'recordinfo' => input('post.recordinfo', null, 'trim'),
'addtime' => time(),
@@ -123,6 +128,7 @@ class Dmonitor extends BaseController
'cycle' => input('post.cycle/d'),
'timeout' => input('post.timeout/d'),
'proxy' => input('post.proxy/d'),
'cdn' => input('post.cdn') == 'true' || input('post.cdn') == '1' ? 1 : 0,
'remark' => input('post.remark', null, 'trim'),
'recordinfo' => input('post.recordinfo', null, 'trim'),
];
@@ -163,8 +169,9 @@ class Dmonitor extends BaseController
}
$domains = [];
foreach (Db::name('domain')->select() as $row) {
$domains[$row['id']] = $row['name'];
$domainList = Db::name('domain')->alias('A')->join('account B', 'A.aid = B.id')->field('A.id,A.name,B.type')->select();
foreach ($domainList as $row) {
$domains[] = ['id'=>$row['id'], 'name'=>$row['name'], 'type'=>$row['type']];
}
View::assign('domains', $domains);

View File

@@ -5,8 +5,9 @@ namespace app\controller;
use app\BaseController;
use think\facade\Db;
use think\facade\View;
use think\facade\Request;
use think\facade\Cache;
use app\lib\DnsHelper;
use app\service\ExpireNoticeService;
use Exception;
class Domain extends BaseController
@@ -57,6 +58,7 @@ class Domain extends BaseController
$sk = input('post.sk', null, 'trim');
$ext = input('post.ext', null, 'trim');
$remark = input('post.remark', null, 'trim');
$proxy = input('post.proxy/d', 0);
if (empty($ak) || empty($sk)) return json(['code' => -1, 'msg' => 'AccessKey和SecretKey不能为空']);
if (Db::name('account')->where('type', $type)->where('ak', $ak)->find()) {
return json(['code' => -1, 'msg' => '域名账户已存在']);
@@ -67,6 +69,7 @@ class Domain extends BaseController
'ak' => $ak,
'sk' => $sk,
'ext' => $ext,
'proxy' => $proxy,
'remark' => $remark,
'addtime' => date('Y-m-d H:i:s'),
]);
@@ -92,6 +95,7 @@ class Domain extends BaseController
$sk = input('post.sk', null, 'trim');
$ext = input('post.ext', null, 'trim');
$remark = input('post.remark', null, 'trim');
$proxy = input('post.proxy/d', 0);
if (empty($ak) || empty($sk)) return json(['code' => -1, 'msg' => 'AccessKey和SecretKey不能为空']);
if (Db::name('account')->where('type', $type)->where('ak', $ak)->where('id', '<>', $id)->find()) {
return json(['code' => -1, 'msg' => '域名账户已存在']);
@@ -102,6 +106,7 @@ class Domain extends BaseController
'ak' => $ak,
'sk' => $sk,
'ext' => $ext,
'proxy' => $proxy,
'remark' => $remark,
]);
$dns = DnsHelper::getModel($id);
@@ -150,11 +155,32 @@ class Domain extends BaseController
return view();
}
public function domain_add()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
$list = Db::name('account')->select();
$accounts = [];
$types = [];
foreach ($list as $row) {
$accounts[$row['id']] = $row['id'] . '_' . DnsHelper::$dns_config[$row['type']]['name'];
if (!array_key_exists($row['type'], $types)) {
$types[$row['type']] = DnsHelper::$dns_config[$row['type']]['name'];
}
if (!empty($row['remark'])) {
$accounts[$row['id']] .= '' . $row['remark'] . '';
}
}
View::assign('accounts', $accounts);
View::assign('types', $types);
return view();
}
public function domain_data()
{
if (!checkPermission(1)) return json(['total' => 0, 'rows' => []]);
$kw = input('post.kw', null, 'trim');
$type = input('post.type', null, 'trim');
$status = input('post.status', null, 'trim');
$offset = input('post.offset/d', 0);
$limit = input('post.limit/d', 10);
@@ -168,6 +194,13 @@ class Domain extends BaseController
if (request()->user['level'] == 1) {
$select->where('is_hide', 0)->where('A.name', 'in', request()->user['permission']);
}
if (!isNullOrEmpty($status)) {
if ($status == '2') {
$select->where('A.expiretime', '<=', date('Y-m-d H:i:s'));
} elseif ($status == '1') {
$select->where('A.expiretime', '<=', date('Y-m-d H:i:s', time() + 86400 * 30))->where('A.expiretime', '>', date('Y-m-d H:i:s'));
}
}
$total = $select->count();
$rows = $select->fieldRaw('A.*,B.type,B.remark aremark')->order('A.id', 'desc')->limit($offset, $limit)->select();
@@ -216,10 +249,13 @@ class Domain extends BaseController
if (!$row) return json(['code' => -1, 'msg' => '域名不存在']);
$is_hide = input('post.is_hide/d');
$is_sso = input('post.is_sso/d');
$is_notice = input('post.is_notice/d');
$remark = input('post.remark', null, 'trim');
if (empty($remark)) $remark = null;
Db::name('domain')->where('id', $id)->update([
'is_hide' => $is_hide,
'is_sso' => $is_sso,
'is_notice' => $is_notice,
'remark' => $remark,
]);
return json(['code' => 0, 'msg' => '修改域名配置成功!']);
@@ -230,6 +266,48 @@ class Domain extends BaseController
Db::name('dmtask')->where('did', $id)->delete();
Db::name('optimizeip')->where('did', $id)->delete();
return json(['code' => 0]);
} elseif ($act == 'batchadd') {
if (!checkPermission(2)) return $this->alert('error', '无权限');
$aid = input('post.aid/d');
$domains = input('post.domains');
if (empty($domains)) return json(['code' => -1, 'msg' => '参数不能为空']);
$data = [];
foreach ($domains as $row) {
$data[] = [
'aid' => $aid,
'name' => $row['name'],
'thirdid' => $row['id'],
'addtime' => date('Y-m-d H:i:s'),
'is_hide' => 0,
'is_sso' => 1,
'recordcount' => $row['recordcount'],
];
}
Db::name('domain')->insertAll($data);
return json(['code' => 0, 'msg' => '成功添加' . count($data) . '个域名!']);
} elseif ($act == 'batchedit') {
if (!checkPermission(2)) return $this->alert('error', '无权限');
$ids = input('post.ids');
if (empty($ids)) return json(['code' => -1, 'msg' => '参数不能为空']);
$remark = input('post.remark', null, 'trim');
if (empty($remark)) $remark = null;
$count = Db::name('domain')->where('id', 'in', $ids)->update(['remark' => $remark]);
return json(['code' => 0, 'msg' => '成功修改' . $count . '个域名!']);
} elseif ($act == 'batchsetnotice') {
if (!checkPermission(2)) return $this->alert('error', '无权限');
$ids = input('post.ids');
$is_notice = input('post.is_notice/d', 0);
if (empty($ids)) return json(['code' => -1, 'msg' => '参数不能为空']);
$count = Db::name('domain')->where('id', 'in', $ids)->update(['is_notice' => $is_notice]);
return json(['code' => 0, 'msg' => '成功修改' . $count . '个域名!']);
} elseif ($act == 'batchdel') {
if (!checkPermission(2)) return $this->alert('error', '无权限');
$ids = input('post.ids');
if (empty($ids)) return json(['code' => -1, 'msg' => '参数不能为空']);
Db::name('domain')->where('id', 'in', $ids)->delete();
Db::name('dmtask')->where('did', 'in', $ids)->delete();
Db::name('optimizeip')->where('did', 'in', $ids)->delete();
return json(['code' => 0, 'msg' => '成功删除' . count($ids) . '个域名!']);
}
return json(['code' => -3]);
}
@@ -245,13 +323,10 @@ class Domain extends BaseController
$result = $dns->getDomainList($kw, $page, $pagesize);
if (!$result) return json(['code' => -1, 'msg' => '获取域名列表失败,' . $dns->getError()]);
$newlist = [];
foreach ($result['list'] as $row) {
if (!Db::name('domain')->where('aid', $aid)->where('name', $row['Domain'])->find()) {
$newlist[] = $row;
}
foreach ($result['list'] as &$row) {
$row['disabled'] = Db::name('domain')->where('aid', $aid)->where('name', $row['Domain'])->find() != null;
}
return json(['code' => 0, 'data' => ['total' => $result['total'], 'list' => $newlist]]);
return json(['code' => 0, 'data' => ['total' => $result['total'], 'list' => $result['list']]]);
}
//获取解析线路和最小TTL
@@ -373,7 +448,7 @@ class Domain extends BaseController
}
$dnstype = Db::name('account')->where('id', $drow['aid'])->value('type');
if ($dnstype == 'baidu' || $dnstype == 'namesilo') {
if (DnsHelper::$dns_config[$dnstype]['page']) {
return json($domainRecords['list']);
}
@@ -382,7 +457,6 @@ class Domain extends BaseController
public function record_list()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
$id = input('post.id/d');
$rr = input('post.rr', null, 'trim');
@@ -398,11 +472,14 @@ class Domain extends BaseController
list($recordLine, $minTTL) = $this->get_line_and_ttl($drow);
$list = [];
foreach ($domainRecords['list'] as &$row) {
if ($rr == '@' && ($row['Type'] == 'NS' || $row['Type'] == 'SOA')) continue;
$row['LineName'] = isset($recordLine[$row['Line']]) ? $recordLine[$row['Line']]['name'] : $row['Line'];
$list[] = $row;
}
return json(['code' => 0, 'data' => $domainRecords['list']]);
return json(['code' => 0, 'data' => $list]);
}
public function record_add()
@@ -430,7 +507,7 @@ class Domain extends BaseController
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
$recordid = $dns->addDomainRecord($name, $type, $value, $line, $ttl, $mx, $weight, $remark);
if ($recordid) {
$this->add_log($drow['name'], '添加解析', $type . '记录 ' . $name . ' ' . $value . ' (线路:' . $line . ' TTL:' . $ttl . ')');
$this->add_log($drow['name'], '添加解析', $name.' ['.$type.'] '.$value.' (线路:'.$line.' TTL:'.$ttl.')');
return json(['code' => 0, 'msg' => '添加解析记录成功!']);
} else {
return json(['code' => -1, 'msg' => '添加解析记录失败,' . $dns->getError()]);
@@ -456,6 +533,9 @@ class Domain extends BaseController
$mx = input('post.mx/d', 1);
$remark = input('post.remark', null, 'trim');
$recordinfo = input('post.recordinfo', null, 'trim');
$recordinfo = json_decode($recordinfo, true);
if (empty($recordid) || empty($name) || empty($type) || empty($value)) {
return json(['code' => -1, 'msg' => '参数不能为空']);
}
@@ -463,7 +543,12 @@ class Domain extends BaseController
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
$recordid = $dns->updateDomainRecord($recordid, $name, $type, $value, $line, $ttl, $mx, $weight, $remark);
if ($recordid) {
$this->add_log($drow['name'], '修改解析', $type . '记录 ' . $name . ' ' . $value . ' (线路:' . $line . ' TTL:' . $ttl . ')');
if (is_array($recordinfo['Value'])) $recordinfo['Value'] = implode(',', $recordinfo['Value']);
if ($recordinfo['Name'] != $name || $recordinfo['Type'] != $type || $recordinfo['Value'] != $value) {
$this->add_log($drow['name'], '修改解析', $recordinfo['Name'].' ['.$recordinfo['Type'].'] '.$recordinfo['Value'].' → '.$name.' ['.$type.'] '.$value.' (线路:'.$line.' TTL:'.$ttl.')');
} elseif($recordinfo['Line'] != $line || $recordinfo['TTL'] != $ttl) {
$this->add_log($drow['name'], '修改解析', $name.' ['.$type.'] '.$value.' (线路:'.$line.' TTL:'.$ttl.')');
}
return json(['code' => 0, 'msg' => '修改解析记录成功!']);
} else {
return json(['code' => -1, 'msg' => '修改解析记录失败,' . $dns->getError()]);
@@ -480,6 +565,8 @@ class Domain extends BaseController
if (!checkPermission(0, $drow['name'])) return $this->alert('error', '无权限');
$recordid = input('post.recordid', null, 'trim');
$recordinfo = input('post.recordinfo', null, 'trim');
$recordinfo = json_decode($recordinfo, true);
if (empty($recordid)) {
return json(['code' => -1, 'msg' => '参数不能为空']);
@@ -487,7 +574,12 @@ class Domain extends BaseController
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
if ($dns->deleteDomainRecord($recordid)) {
$this->add_log($drow['name'], '删除解析', '记录ID:' . $recordid);
if ($recordinfo) {
if (is_array($recordinfo['Value'])) $recordinfo['Value'] = implode(',', $recordinfo['Value']);
$this->add_log($drow['name'], '删除解析', $recordinfo['Name'].' ['.$recordinfo['Type'].'] '.$recordinfo['Value'].' (线路:'.$recordinfo['Line'].' TTL:'.$recordinfo['TTL'].')');
} else {
$this->add_log($drow['name'], '删除解析', '记录ID:'.$recordid);
}
return json(['code' => 0, 'msg' => '删除解析记录成功!']);
} else {
return json(['code' => -1, 'msg' => '删除解析记录失败,' . $dns->getError()]);
@@ -505,6 +597,8 @@ class Domain extends BaseController
$recordid = input('post.recordid', null, 'trim');
$status = input('post.status', null, 'trim');
$recordinfo = input('post.recordinfo', null, 'trim');
$recordinfo = json_decode($recordinfo, true);
if (empty($recordid)) {
return json(['code' => -1, 'msg' => '参数不能为空']);
@@ -513,7 +607,12 @@ class Domain extends BaseController
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
if ($dns->setDomainRecordStatus($recordid, $status)) {
$action = $status == '1' ? '启用解析' : '暂停解析';
$this->add_log($drow['name'], $action, '记录ID:' . $recordid);
if ($recordinfo) {
if (is_array($recordinfo['Value'])) $recordinfo['Value'] = implode(',', $recordinfo['Value']);
$this->add_log($drow['name'], $action, $recordinfo['Name'].' ['.$recordinfo['Type'].'] '.$recordinfo['Value'].' (线路:'.$recordinfo['Line'].' TTL:'.$recordinfo['TTL'].')');
} else {
$this->add_log($drow['name'], $action, '记录ID:'.$recordid);
}
return json(['code' => 0, 'msg' => '操作成功!']);
} else {
return json(['code' => -1, 'msg' => '操作失败,' . $dns->getError()]);
@@ -554,10 +653,11 @@ class Domain extends BaseController
}
if (!checkPermission(0, $drow['name'])) return $this->alert('error', '无权限');
$recordids = input('post.recordids', null, 'trim');
$action = input('post.action', null, 'trim');
$recordinfo = input('post.recordinfo', null, 'trim');
$recordinfo = json_decode($recordinfo, true);
if (empty($recordids) || empty($action)) {
if (empty($recordinfo) || empty($action)) {
return json(['code' => -1, 'msg' => '参数不能为空']);
}
@@ -565,25 +665,28 @@ class Domain extends BaseController
$fail = 0;
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
if ($action == 'open') {
foreach ($recordids as $recordid) {
if ($dns->setDomainRecordStatus($recordid, '1')) {
$this->add_log($drow['name'], '启用解析', '记录ID:' . $recordid);
foreach ($recordinfo as $record) {
if ($dns->setDomainRecordStatus($record['RecordId'], '1')) {
if (is_array($record['Value'])) $record['Value'] = implode(',', $record['Value']);
$this->add_log($drow['name'], '启用解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' (线路:'.$record['Line'].' TTL:'.$record['TTL'].')');
$success++;
}
}
$msg = '成功启用' . $success . '条解析记录';
} else if ($action == 'pause') {
foreach ($recordids as $recordid) {
if ($dns->setDomainRecordStatus($recordid, '0')) {
$this->add_log($drow['name'], '暂停解析', '记录ID:' . $recordid);
foreach ($recordinfo as $record) {
if ($dns->setDomainRecordStatus($record['RecordId'], '0')) {
if (is_array($record['Value'])) $record['Value'] = implode(',', $record['Value']);
$this->add_log($drow['name'], '暂停解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' (线路:'.$record['Line'].' TTL:'.$record['TTL'].')');
$success++;
}
}
$msg = '成功暂停' . $success . '条解析记录';
} else if ($action == 'delete') {
foreach ($recordids as $recordid) {
if ($dns->deleteDomainRecord($recordid)) {
$this->add_log($drow['name'], '删除解析', '记录ID:' . $recordid);
foreach ($recordinfo as $record) {
if ($dns->deleteDomainRecord($record['RecordId'])) {
if (is_array($record['Value'])) $record['Value'] = implode(',', $record['Value']);
$this->add_log($drow['name'], '删除解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' (线路:'.$record['Line'].' TTL:'.$record['TTL'].')');
$success++;
}
}
@@ -591,8 +694,8 @@ class Domain extends BaseController
} else if ($action == 'remark') {
$remark = input('post.remark', null, 'trim');
if (empty($remark)) $remark = null;
foreach ($recordids as $recordid) {
if ($dns->updateDomainRecordRemark($recordid, $remark)) {
foreach ($recordinfo as $record) {
if ($dns->updateDomainRecordRemark($record['RecordId'], $remark)) {
$success++;
} else {
$fail++;
@@ -629,9 +732,10 @@ class Domain extends BaseController
$success = 0;
$fail = 0;
foreach ($recordinfo as $record) {
$recordid = $dns->updateDomainRecord($record['recordid'], $record['name'], $type, $value, $record['line'], $record['ttl'], $record['mx'], $record['weight'], $record['remark']);
$recordid = $dns->updateDomainRecord($record['RecordId'], $record['Name'], $type, $value, $record['Line'], $record['TTL'], $record['MX'], $record['Weight'], $record['Remark']);
if ($recordid) {
$this->add_log($drow['name'], '修改解析', $type . '记录 ' . $record['name'] . ' ' . $value . ' (线路:' . $record['line'] . ' TTL:' . $record['ttl'] . ')');
if (is_array($record['Value'])) $record['Value'] = implode(',', $record['Value']);
$this->add_log($drow['name'], '修改解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' → '.$record['Name'].' ['.$type.'] '.$value.' (线路:'.$record['Line'].' TTL:'.$record['TTL'].')');
$success++;
} else {
$fail++;
@@ -650,9 +754,10 @@ class Domain extends BaseController
$success = 0;
$fail = 0;
foreach ($recordinfo as $record) {
$recordid = $dns->updateDomainRecord($record['recordid'], $record['name'], $record['type'], $record['value'], $line, $record['ttl'], $record['mx'], $record['weight'], $record['remark']);
$recordid = $dns->updateDomainRecord($record['RecordId'], $record['Name'], $record['Type'], $record['Value'], $line, $record['TTL'], $record['MX'], $record['Weight'], $record['Remark']);
if ($recordid) {
$this->add_log($drow['name'], '修改解析', $record['type'] . '记录 ' . $record['name'] . ' ' . $record['value'] . ' (线路:' . $line . ' TTL:' . $record['ttl'] . ')');
if (is_array($record['Value'])) $record['Value'] = implode(',', $record['Value']);
$this->add_log($drow['name'], '修改解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' (线路:'.$line.' TTL:'.$record['TTL'].')');
$success++;
} else {
$fail++;
@@ -683,6 +788,9 @@ class Domain extends BaseController
if (empty($record) || empty($recordlist)) {
return json(['code' => -1, 'msg' => '参数不能为空']);
}
if (is_null($line)) {
$line = DnsHelper::$line_name[$dnstype]['DEF'];
}
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
@@ -695,13 +803,19 @@ class Domain extends BaseController
$thistype = empty($type) ? getDnsType($arr[1]) : $type;
$recordid = $dns->addDomainRecord($arr[0], $thistype, $arr[1], $line, $ttl, $mx);
if ($recordid) {
$this->add_log($drow['name'], '添加解析', $thistype . '记录 ' . $arr[0] . ' ' . $arr[1] . ' (线路:' . $line . ' TTL:' . $ttl . ')');
$this->add_log($drow['name'], '添加解析', $arr[0].' ['.$thistype.'] '.$arr[1].' (线路:'.$line.' TTL:'.$ttl.')');
$success++;
} else {
$fail++;
}
}
return json(['code' => 0, 'msg' => '批量添加解析,成功' . $success . '条,失败' . $fail . '条']);
if ($success > 0) {
return json(['code' => 0, 'msg' => '批量添加解析,成功' . $success . '条,失败' . $fail . '条']);
} elseif($fail > 0) {
return json(['code' => -1, 'msg' => '批量添加解析失败,' . $dns->getError()]);
} else {
return json(['code' => -1, 'msg' => '批量添加解析失败,没有可添加的记录']);
}
}
list($recordLine, $minTTL) = $this->get_line_and_ttl($drow);
@@ -722,6 +836,89 @@ class Domain extends BaseController
return view('batchadd');
}
public function record_batch_add2()
{
return view('batchadd2');
}
public function record_batch_edit2()
{
if (request()->isAjax()) {
$id = input('post.id/d');
$drow = Db::name('domain')->where('id', $id)->find();
if (!$drow) {
return json(['code' => -1, 'msg' => '域名不存在']);
}
$dnstype = Db::name('account')->where('id', $drow['aid'])->value('type');
if (!checkPermission(0, $drow['name'])) return json(['code' => -1, 'msg' => '无权限']);
$name = input('post.name', null, 'trim');
$type = input('post.type', null, 'trim');
$value = input('post.value', null, 'trim');
$ttl = input('post.ttl/d', 0);
$mx = input('post.mx/d', 0);
if (empty($name) || empty($type) || empty($value)) {
return json(['code' => -1, 'msg' => '必填参数不能为空']);
}
$line = DnsHelper::$line_name[$dnstype]['DEF'];
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
$domainRecords = $dns->getSubDomainRecords($name, 1, 100);
if (!$domainRecords) return json(['code' => -1, 'msg' => '获取记录列表失败,' . $dns->getError()]);
if (empty($domainRecords['list'])) return json(['code' => -1, 'msg' => '没有可修改的记录']);
if ($type == 'A' || $type == 'AAAA' || $type == 'CNAME') {
$list2 = array_filter($domainRecords['list'], function ($item) use ($type) {
return $item['Type'] == $type;
});
if (!empty($list2)) {
$list = $list2;
} else {
$list = array_filter($domainRecords['list'], function ($item) {
return $item['Type'] == 'A' || $item['Type'] == 'AAAA' || $item['Type'] == 'CNAME';
});
}
} else {
$list = array_filter($domainRecords['list'], function ($item) use ($type) {
return $item['Type'] == $type;
});
}
if (empty($list)) return json(['code' => -1, 'msg' => '没有可修改的'.$type.'记录']);
$list2 = array_filter($domainRecords['list'], function ($item) use ($line) {
return $item['Line'] == $line;
});
if (!empty($list2)) $list = $list2;
$success = 0;
$fail = 0;
foreach ($list as $record) {
if ($name == '@' && ($record['Type'] == 'NS' || $record['Type'] == 'SOA')) continue;
if ($ttl > 0) $record['TTL'] = $ttl;
if ($mx > 0) $record['MX'] = $mx;
$recordid = $dns->updateDomainRecord($record['RecordId'], $record['Name'], $type, $value, $record['Line'], $record['TTL'], $record['MX'], $record['Weight'], $record['Remark']);
if ($recordid) {
if (is_array($record['Value'])) $record['Value'] = implode(',', $record['Value']);
$this->add_log($drow['name'], '修改解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' → '.$record['Name'].' ['.$type.'] '.$value.' (线路:'.$record['Line'].' TTL:'.$record['TTL'].')');
$success++;
} else {
$fail++;
}
}
if ($success > 0) {
return json(['code' => 0, 'msg' => '成功修改' . $success . '条解析记录']);
} elseif($fail > 0) {
return json(['code' => -1, 'msg' => $dns->getError()]);
} else {
return json(['code' => -1, 'msg' => '没有可修改的记录']);
}
}
return view('batchedit');
}
public function record_log()
{
$id = input('param.id/d');
@@ -748,6 +945,135 @@ class Domain extends BaseController
private function add_log($domain, $action, $data)
{
if (strlen($data) > 500) $data = substr($data, 0, 500);
Db::name('log')->insert(['uid' => request()->user['id'], 'domain' => $domain, 'action' => $action, 'data' => $data, 'addtime' => date("Y-m-d H:i:s")]);
}
public function weight()
{
$id = input('param.id/d');
$drow = Db::name('domain')->where('id', $id)->find();
if (!$drow) {
return $this->alert('error', '域名不存在');
}
if (!checkPermission(0, $drow['name'])) return $this->alert('error', '无权限');
if (request()->isAjax()) {
$act = input('param.act');
if ($act == 'status') {
$subdomain = input('post.subdomain', null, 'trim');
$status = input('post.status', null, 'trim');
$type = input('post.type', null, 'trim');
$line = input('post.line', null, 'trim');
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
if ($dns->setWeightStatus($subdomain, $status, $type, $line)) {
return json(['code' => 0, 'msg' => '操作成功']);
} else {
return json(['code' => -1, 'msg' => '操作失败,' . $dns->getError()]);
}
} elseif ($act == 'update') {
$subdomain = input('post.subdomain', null, 'trim');
$status = input('post.status', '0', 'trim');
$type = input('post.type', null, 'trim');
$line = input('post.line', null, 'trim');
$weight = input('post.weight');
if (empty($subdomain) || empty($type) || empty($line) || $status == '1' && empty($weight)) {
return json(['code' => -1, 'msg' => '参数不能为空']);
}
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
if ($dns->setWeightStatus($subdomain, $status, $type, $line)) {
if ($status == '1') {
$success = 0;
foreach($weight as $recordid => $weight) {
if ($dns->updateRecordWeight($recordid, $weight)) {
$success++;
}
}
if ($success > 0) {
return json(['code' => 0, 'msg' => '成功修改' . $success . '条解析记录权重']);
} else {
return json(['code' => -1, 'msg' => '修改权重失败,' . $dns->getError()]);
}
}
return json(['code' => 0, 'msg' => '修改成功']);
} else {
return json(['code' => -1, 'msg' => '修改失败,' . $dns->getError()]);
}
} else {
return json(['code' => -1, 'msg' => '参数错误']);
}
}
$dnstype = Db::name('account')->where('id', $drow['aid'])->value('type');
if ($dnstype != 'aliyun') {
return $this->alert('error', '仅支持阿里云解析的域名');
}
list($recordLine, $minTTL) = $this->get_line_and_ttl($drow);
$recordLineArr = [];
foreach ($recordLine as $key => $item) {
$recordLineArr[] = ['id' => strval($key), 'name' => $item['name'], 'parent' => $item['parent']];
}
$dnsconfig = DnsHelper::$dns_config[$dnstype];
$dnsconfig['type'] = $dnstype;
View::assign('domainId', $id);
View::assign('domainName', $drow['name']);
View::assign('recordLine', $recordLineArr);
View::assign('dnsconfig', $dnsconfig);
return view();
}
public function weight_data()
{
$id = input('param.id/d');
$keyword = input('post.keyword', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
if ($limit == 0) {
$page = 1;
} else {
$page = $offset / $limit + 1;
}
$drow = Db::name('domain')->where('id', $id)->find();
if (!$drow) {
return json(['total' => 0, 'rows' => []]);
}
if (!checkPermission(0, $drow['name'])) return json(['total' => 0, 'rows' => []]);
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
$domainRecords = $dns->getWeightSubDomains($page, $limit, $keyword);
return json(['total' => $domainRecords['total'], 'rows' => $domainRecords['list']]);
}
public function expire_notice()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
if ($this->request->isPost()) {
$params = input('post.');
foreach ($params as $key => $value) {
if (empty($key)) {
continue;
}
config_set($key, $value);
Cache::delete('configs');
}
return json(['code' => 0, 'msg' => 'succ']);
}
return View::fetch();
}
public function update_date()
{
$id = input('param.id/d');
$drow = Db::name('domain')->where('id', $id)->find();
if (!$drow) {
return json(['code' => -1, 'msg' => '域名不存在']);
}
if (!checkPermission(0, $drow['name'])) return json(['code' => -1, 'msg' => '无权限']);
$result = (new ExpireNoticeService())->updateDomainDate($id, $drow['name']);
return json($result);
}
}

View File

@@ -19,16 +19,36 @@ class Index extends BaseController
}
if ($this->request->isAjax()) {
if (input('post.do') == 'stat') {
$stat = ['domains' => 0, 'users' => 0, 'records' => 0, 'types' => count(DnsHelper::$dns_config)];
$stat = [];
if ($this->request->user['level'] == 2) {
$stat['domains'] = Db::name('domain')->count();
$stat['users'] = Db::name('user')->count();
$stat['records'] = Db::name('domain')->sum('recordcount');
} else {
$stat['domains'] = Db::name('domain')->where('name', 'in', $this->request->user['permission'])->count();
$stat['users'] = 1;
$stat['records'] = Db::name('domain')->where('name', 'in', $this->request->user['permission'])->sum('recordcount');
}
$stat['tasks'] = Db::name('dmtask')->count();
$stat['certs'] = Db::name('cert_order')->count();
$stat['deploys'] = Db::name('cert_deploy')->count();
$run_time = config_get('run_time', null, true);
$run_state = $run_time ? (time() - strtotime($run_time) > 10 ? 0 : 1) : 0;
$stat['dmonitor_state'] = $run_state;
$stat['dmonitor_active'] = Db::name('dmtask')->where('active', 1)->count();
$stat['dmonitor_status_0'] = Db::name('dmtask')->where('status', 0)->count();
$stat['dmonitor_status_1'] = Db::name('dmtask')->where('status', 1)->count();
$stat['optimizeip_active'] = Db::name('optimizeip')->where('active', 1)->count();
$stat['optimizeip_status_1'] = Db::name('optimizeip')->where('status', 1)->count();
$stat['optimizeip_status_2'] = Db::name('optimizeip')->where('status', 2)->count();
$stat['certorder_status_3'] = Db::name('cert_order')->where('status', 3)->count();
$stat['certorder_status_5'] = Db::name('cert_order')->where('status', '<', 0)->count();
$stat['certorder_status_6'] = Db::name('cert_order')->where('expiretime', '<', date('Y-m-d H:i:s', time() + 86400 * 7))->where('expiretime', '>=', date('Y-m-d H:i:s'))->count();
$stat['certorder_status_7'] = Db::name('cert_order')->where('expiretime', '<', date('Y-m-d H:i:s'))->count();
$stat['certdeploy_status_0'] = Db::name('cert_deploy')->where('status', 0)->count();
$stat['certdeploy_status_1'] = Db::name('cert_deploy')->where('status', 1)->count();
$stat['certdeploy_status_2'] = Db::name('cert_deploy')->where('status', -1)->count();
return json($stat);
}
return json(['code' => -3]);

View File

@@ -39,6 +39,7 @@ class Optimizeip extends BaseController
if (!checkPermission(2)) return json(['total' => 0, 'rows' => []]);
$type = input('post.type/d', 1);
$kw = input('post.kw', null, 'trim');
$status = input('post.status', null);
$offset = input('post.offset/d');
$limit = input('post.limit/d');
@@ -50,6 +51,9 @@ class Optimizeip extends BaseController
$select->whereLike('remark', '%' . $kw . '%');
}
}
if (!isNullOrEmpty($status)) {
$select->where('status', intval($status));
}
$total = $select->count();
$list = $select->order('A.id', 'desc')->limit($offset, $limit)->field('A.*,B.name domain')->select();

View File

@@ -76,6 +76,20 @@ class System extends BaseController
}
}
public function webhooktest()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
$webhook_url = config_get('webhook_url');
if (empty($webhook_url)) return json(['code' => -1, 'msg' => '请先保存设置']);
$content = "这是一封测试消息!\n来自:" . $this->request->root(true);
$result = \app\utils\MsgNotice::send_webhook('消息发送测试', $content);
if ($result === true) {
return json(['code' => 0, 'msg' => '消息发送成功!']);
} else {
return json(['code' => -1, 'msg' => '消息发送失败!' . $result]);
}
}
public function proxytest()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');

View File

@@ -1922,4 +1922,7 @@ com.za
com.zw
mil.cn
edu.kg
edu.cn
edu.cn
eu.org
us.kg
ggff.net

View File

@@ -154,6 +154,15 @@ class CertHelper
'placeholder' => '申请证书时填写的邮箱',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'aliyun' => [
@@ -195,6 +204,15 @@ class CertHelper
'placeholder' => '申请联系人的邮箱地址',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'ucloud' => [

View File

@@ -43,6 +43,7 @@ class DeployHelper
'options' => [
'0' => '宝塔面板站点的证书',
'1' => '宝塔面板本身的证书',
'2' => '宝塔邮局域名的证书',
],
'value' => '0',
'required' => true,
@@ -51,14 +52,14 @@ class DeployHelper
'name' => '网站名称列表',
'type' => 'textarea',
'placeholder' => '填写要部署证书的网站名称,每行一个',
'note' => 'PHP项目和反代项目填写创建时绑定的第一个域名Java/Node/Go等其他项目填写项目名称',
'show' => 'type==0',
'note' => 'PHP项目和反代项目填写创建时绑定的第一个域名Java/Node/Go等其他项目填写项目名称,邮局填写域名',
'show' => 'type==0||type==2',
'required' => true,
],
],
],
'kangle' => [
'name' => 'Kangle',
'name' => 'Kangle用户',
'class' => 1,
'icon' => 'host.png',
'note' => '以上登录信息为Easypanel用户面板的非管理员面板。如选网站密码认证类型则用户面板登录不能开启验证码。',
@@ -130,6 +131,72 @@ class DeployHelper
],
],
],
'kangleadmin' => [
'name' => 'Kangle管理员',
'class' => 1,
'icon' => 'host.png',
'note' => '以上登录地址需填写Easypanel管理员面板地址非用户面板。',
'inputs' => [
'url' => [
'name' => '面板地址',
'type' => 'input',
'placeholder' => 'Easypanel管理员面板地址',
'note' => '填写规则如http://192.168.1.100:3312 ,不要带其他后缀',
'required' => true,
],
'path' => [
'name' => '管理员面板路径',
'type' => 'input',
'placeholder' => '留空默认为/admin',
],
'username' => [
'name' => '管理员用户名',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'skey' => [
'name' => '面板安全码',
'type' => 'input',
'placeholder' => '管理员面板->服务器设置->面板通信安全码',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'name' => [
'name' => '网站用户名',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'type' => [
'name' => '部署类型',
'type' => 'radio',
'options' => [
'0' => '网站SSL证书',
'1' => '单域名SSL证书仅CDN支持',
],
'value' => '0',
'required' => true,
],
'domains' => [
'name' => 'CDN域名列表',
'type' => 'textarea',
'placeholder' => '填写要部署证书的域名,每行一个',
'show' => 'type==1',
'required' => true,
],
],
],
'safeline' => [
'name' => '雷池WAF',
'class' => 1,
@@ -259,16 +326,6 @@ class DeployHelper
'note' => '需要先<a href="https://goedge.cloud/docs/API/Settings.md" target="_blank" rel="noreferrer">开启HTTP API端口</a>',
'tasknote' => '系统会根据关联SSL证书的域名自动更新对应证书',
'inputs' => [
'systype' => [
'name' => '系统类型',
'type' => 'radio',
'options' => [
'0' => 'GoEdge',
'1' => 'FlexCDN',
],
'value' => '0',
'required' => true,
],
'url' => [
'name' => 'HTTP API地址',
'type' => 'input',
@@ -298,6 +355,16 @@ class DeployHelper
'value' => 'user',
'required' => true,
],
'systype' => [
'name' => '系统类型',
'type' => 'radio',
'options' => [
'0' => 'GoEdge',
'1' => 'FlexCDN',
],
'value' => '0',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
@@ -342,6 +409,163 @@ class DeployHelper
],
'taskinputs' => [],
],
'mwpanel' => [
'name' => 'MW面板',
'class' => 1,
'icon' => 'mwpanel.ico',
'note' => null,
'tasknote' => '',
'inputs' => [
'url' => [
'name' => '面板地址',
'type' => 'input',
'placeholder' => 'MW面板地址',
'note' => '填写规则如http://192.168.1.100:8888 ,不要带其他后缀',
'required' => true,
],
'appid' => [
'name' => '应用ID',
'type' => 'input',
'placeholder' => 'MW面板设置->API接口',
'required' => true,
],
'appsecret' => [
'name' => '应用密钥',
'type' => 'input',
'placeholder' => '面板设置->API接口',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'type' => [
'name' => '部署类型',
'type' => 'radio',
'options' => [
'0' => 'MW面板站点的证书',
'1' => 'MW面板本身的证书',
],
'value' => '0',
'required' => true,
],
'sites' => [
'name' => '网站名称列表',
'type' => 'textarea',
'placeholder' => '填写要部署证书的网站名称,每行一个',
'note' => '网站名称,即为网站创建时绑定的第一个域名',
'show' => 'type==0',
'required' => true,
],
],
],
'synology' => [
'name' => '群晖面板',
'class' => 1,
'icon' => 'synology.png',
'note' => null,
'tasknote' => '',
'inputs' => [
'url' => [
'name' => '面板地址',
'type' => 'input',
'placeholder' => '群晖面板地址',
'note' => '填写规则如http://192.168.1.100:5000 ,不要带其他后缀',
'required' => true,
],
'username' => [
'name' => '登录账号',
'type' => 'input',
'placeholder' => '必须是处于管理员用户组,不能开启双重认证',
'required' => true,
],
'password' => [
'name' => '登录密码',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'version' => [
'name' => '群晖版本',
'type' => 'radio',
'options' => [
'0' => '7.x',
'1' => '6.x',
],
'value' => '0',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'desc' => [
'name' => '群晖证书描述',
'type' => 'input',
'placeholder' => '',
'note' => '根据证书描述匹配替换对应证书,留空则根据证书通用名匹配',
],
],
],
'proxmox' => [
'name' => 'Proxmox VE',
'class' => 1,
'icon' => 'proxmox.ico',
'note' => '在“权限->API令牌”添加令牌不要选特权分离',
'tasknote' => '',
'inputs' => [
'url' => [
'name' => '面板地址',
'type' => 'input',
'placeholder' => 'Proxmox VE 面板地址',
'note' => '填写规则如https://192.168.1.100:8006 ,不要带其他后缀',
'required' => true,
],
'api_user' => [
'name' => 'API令牌ID',
'type' => 'input',
'placeholder' => '用户!令牌名称',
'required' => true,
],
'api_key' => [
'name' => 'API令牌密钥',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'node' => [
'name' => '节点名称',
'type' => 'input',
'placeholder' => '要部署证书的节点',
'required' => true,
],
],
],
'aliyun' => [
'name' => '阿里云',
'class' => 2,
@@ -361,6 +585,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
@@ -499,6 +732,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
@@ -592,7 +834,7 @@ class DeployHelper
'name' => '实例ID',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'lighthouse\'',
'show' => 'product==\'lighthouse\'||product==\'ddos\'',
'required' => true,
],
'domain' => [
@@ -600,6 +842,7 @@ class DeployHelper
'type' => 'input',
'placeholder' => '',
'show' => 'product!=\'clb\'&&product!=\'tke\'',
'note' => 'CDN、EO、WAF多个域名可用,隔开其他只能填写1个域名',
'required' => true,
],
],
@@ -622,6 +865,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
@@ -638,7 +890,7 @@ class DeployHelper
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
'placeholder' => '',
'placeholder' => '多个域名可使用,分隔',
'show' => 'product==\'cdn\'',
'required' => true,
],
@@ -714,6 +966,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
@@ -722,10 +983,18 @@ class DeployHelper
'options' => [
['value'=>'cdn', 'label'=>'CDN'],
['value'=>'oss', 'label'=>'OSS'],
['value'=>'pili', 'label'=>'视频直播'],
],
'value' => 'cdn',
'required' => true,
],
'pili_hub' => [
'name' => '直播空间名称',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'pili\'',
'required' => true,
],
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
@@ -752,6 +1021,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'domain' => [
@@ -762,6 +1040,35 @@ class DeployHelper
],
],
],
'upyun' => [
'name' => '又拍云',
'class' => 2,
'icon' => 'upyun.ico',
'tasknote' => '系统会根据关联SSL证书的域名进行证书的迁移操作',
'inputs' => [
'username' => [
'name' => '用户名',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'password' => [
'name' => '密码',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
],
'baidu' => [
'name' => '百度云',
'class' => 2,
@@ -780,6 +1087,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'domain' => [
@@ -808,12 +1124,88 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
'name' => '要部署的产品',
'type' => 'select',
'options' => [
['value'=>'cdn', 'label'=>'内容分发网络CDN'],
['value'=>'dcdn', 'label'=>'全站加速DCDN'],
['value'=>'clb', 'label'=>'负载均衡CLB'],
['value'=>'alb', 'label'=>'应用型负载均衡ALB'],
['value'=>'tos', 'label'=>'对象存储TOS'],
['value'=>'live', 'label'=>'视频直播'],
['value'=>'imagex', 'label'=>'veImageX'],
],
'value' => 'cdn',
'required' => true,
],
'bucket_domain' => [
'name' => 'Bucket域名',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'tos\'',
'required' => true,
],
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
'placeholder' => '多个域名可使用英文逗号分隔',
'placeholder' => '多个域名可使用,分隔',
'show' => 'product!=\'clb\'&&product!=\'alb\'',
'required' => true,
],
'listener_id' => [
'name' => '监听器ID',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'clb\'||product==\'alb\'',
'required' => true,
],
],
],
'west' => [
'name' => '西部数码',
'class' => 2,
'icon' => 'west.ico',
'note' => '支持部署到西部数码虚拟主机',
'inputs' => [
'username' => [
'name' => '用户名',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'api_password' => [
'name' => 'API密码',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'sitename' => [
'name' => 'FTP账号',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
],
@@ -857,6 +1249,93 @@ class DeployHelper
],
],
],
'ctyun' => [
'name' => '天翼云',
'class' => 2,
'icon' => 'ctyun.ico',
'note' => '支持部署到天翼云CDN',
'inputs' => [
'AccessKeyId' => [
'name' => 'AccessKeyId',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'SecretAccessKey' => [
'name' => 'SecretAccessKey',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
'name' => '要部署的产品',
'type' => 'select',
'options' => [
['value'=>'cdn', 'label'=>'CDN加速'],
['value'=>'icdn', 'label'=>'全站加速'],
['value'=>'accessone', 'label'=>'边缘安全加速平台'],
],
'value' => 'cdn',
'required' => true,
],
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
],
],
'kuocai' => [
'name' => '括彩云',
'class' => 2,
'icon' => 'kuocai.jpg',
'note' => '支持括彩云及其代理商,填写控制台登录账号及密码',
'inputs' => [
'username' => [
'name' => '账号',
'type' => 'input',
'placeholder' => '控制台账号',
'note' => '填写手机号或邮箱',
'required' => true,
],
'password' => [
'name' => '密码',
'type' => 'input',
'placeholder' => '控制台密码',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'id' => [
'name' => '域名ID',
'type' => 'input',
'placeholder' => '',
'note' => '在控制台->我的域名->配置复制浏览器地址栏显示的域名ID19位数字注意域名是否与证书匹配',
'required' => true,
],
],
],
'allwaf' => [
'name' => 'AllWAF',
'class' => 2,
@@ -906,6 +1385,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
@@ -1048,7 +1536,7 @@ class DeployHelper
'privatekey' => [
'name' => '私钥',
'type' => 'textarea',
'placeholder' => '填写私钥内容',
'placeholder' => '填写PEM格式私钥内容',
'required' => true,
'show' => 'auth==1',
],
@@ -1266,7 +1754,7 @@ class DeployHelper
];
public static $class_config = [
1 => '自建面板',
1 => '自建系统',
2 => '云服务商',
3 => '服务器',
];

View File

@@ -18,6 +18,7 @@ class DnsHelper
'redirect' => true, //是否支持域名转发
'log' => true, //是否支持查看日志
'weight' => false, //是否支持权重
'page' => false, //是否客户端分页
],
'dnspod' => [
'name' => '腾讯云',
@@ -30,6 +31,7 @@ class DnsHelper
'redirect' => true,
'log' => true,
'weight' => true,
'page' => false,
],
'huawei' => [
'name' => '华为云',
@@ -42,6 +44,7 @@ class DnsHelper
'redirect' => false,
'log' => false,
'weight' => true,
'page' => false,
],
'baidu' => [
'name' => '百度云',
@@ -54,6 +57,7 @@ class DnsHelper
'redirect' => false,
'log' => false,
'weight' => false,
'page' => true,
],
'west' => [
'name' => '西部数码',
@@ -66,6 +70,7 @@ class DnsHelper
'redirect' => false,
'log' => false,
'weight' => false,
'page' => false,
],
'huoshan' => [
'name' => '火山引擎',
@@ -78,6 +83,20 @@ class DnsHelper
'redirect' => false,
'log' => false,
'weight' => true,
'page' => false,
],
'jdcloud' => [
'name' => '京东云',
'config' => [
'ak' => 'AccessKeyId',
'sk' => 'AccessKeySecret',
],
'remark' => 0,
'status' => true,
'redirect' => true,
'log' => false,
'weight' => true,
'page' => false,
],
'dnsla' => [
'name' => 'DNSLA',
@@ -90,6 +109,7 @@ class DnsHelper
'redirect' => true,
'log' => false,
'weight' => true,
'page' => false,
],
'cloudflare' => [
'name' => 'Cloudflare',
@@ -98,10 +118,11 @@ class DnsHelper
'sk' => 'API密钥/令牌',
],
'remark' => 2,
'status' => false,
'status' => true,
'redirect' => false,
'log' => false,
'weight' => false,
'page' => false,
],
'namesilo' => [
'name' => 'NameSilo',
@@ -114,6 +135,21 @@ class DnsHelper
'redirect' => false,
'log' => false,
'weight' => false,
'page' => true,
],
'powerdns' => [
'name' => 'PowerDNS',
'config' => [
'ak' => 'IP地址',
'sk' => '端口',
'ext' => 'API KEY',
],
'remark' => 2,
'status' => true,
'redirect' => false,
'log' => false,
'weight' => false,
'page' => true,
],
];
@@ -125,7 +161,10 @@ class DnsHelper
'dnsla' => ['DEF' => '', 'CT' => '84613316902921216', 'CU' => '84613316923892736', 'CM' => '84613316953252864', 'AB' => ''],
'huoshan' => ['DEF' => 'default', 'CT' => 'telecom', 'CU' => 'unicom', 'CM' => 'mobile', 'AB' => 'oversea'],
'baidu' => ['DEF' => 'default', 'CT' => 'ct', 'CU' => 'cnc', 'CM' => 'cmnet', 'AB' => ''],
'jdcloud' => ['DEF' => '-1', 'CT' => '1', 'CU' => '2', 'CM' => '3', 'AB' => '4'],
'cloudflare' => ['DEF' => '0'],
'namesilo' => ['DEF' => 'default'],
'powerdns' => ['DEF' => 'default'],
];
public static function getList()

View File

@@ -227,7 +227,7 @@ class ACMECert extends ACMEv2
public function authOrder($order)
{
if ($order['status'] != 'ready' && empty($order['challenges'])) {
if ($order['status'] != 'pending' && $order['status'] != 'ready' && empty($order['challenges'])) {
throw new Exception('No challenges available');
}

View File

@@ -308,26 +308,7 @@ class ACMEv2
));
if ($this->proxy) {
$proxy_server = config_get('proxy_server');
$proxy_port = intval(config_get('proxy_port'));
$proxy_userpwd = config_get('proxy_user').':'.config_get('proxy_pwd');
$proxy_type = config_get('proxy_type');
if ($proxy_type == 'https') {
$proxy_type = CURLPROXY_HTTPS;
} elseif ($proxy_type == 'sock4') {
$proxy_type = CURLPROXY_SOCKS4;
} elseif ($proxy_type == 'sock5') {
$proxy_type = CURLPROXY_SOCKS5;
} else {
$proxy_type = CURLPROXY_HTTP;
}
curl_setopt($this->ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($this->ch, CURLOPT_PROXY, $proxy_server);
curl_setopt($this->ch, CURLOPT_PROXYPORT, $proxy_port);
if ($proxy_userpwd != ':') {
curl_setopt($this->ch, CURLOPT_PROXYUSERPWD, $proxy_userpwd);
}
curl_setopt($this->ch, CURLOPT_PROXYTYPE, $proxy_type);
curl_set_proxy($this->ch);
}
$took = microtime(true);

View File

@@ -20,7 +20,8 @@ class aliyun implements CertInterface
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->AccessKeySecret = $config['AccessKeySecret'];
$this->client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $this->Endpoint, $this->Version);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $this->Endpoint, $this->Version, $proxy);
$this->config = $config;
}

View File

@@ -21,7 +21,8 @@ class huoshan implements CertInterface
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $this->service, $this->version, $this->region);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $this->service, $this->version, $this->region, $proxy);
}
public function register()

View File

@@ -21,7 +21,8 @@ class tencent implements CertInterface
{
$this->SecretId = $config['SecretId'];
$this->SecretKey = $config['SecretKey'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, $this->endpoint, $this->service, $this->version);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, $this->endpoint, $this->service, $this->version, null, $proxy);
$this->email = $config['email'];
}

View File

@@ -16,8 +16,9 @@ class AWS
private $version;
private $region;
private $etag;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $service, $version, $region)
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $service, $version, $region, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
@@ -25,6 +26,7 @@ class AWS
$this->service = $service;
$this->version = $version;
$this->region = $region;
$this->proxy = $proxy;
}
/**
@@ -142,6 +144,7 @@ class AWS
}
$path = '/' . $this->version . $path;
$body = '';
if ($method == 'GET' || $method == 'DELETE') {
$query = $params;
} else {
@@ -179,7 +182,7 @@ class AWS
// step 1: build canonical request string
$httpRequestMethod = $method;
$canonicalUri = $path;
$canonicalUri = $this->getCanonicalURI($path);
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
@@ -219,6 +222,17 @@ class AWS
$replace = ['%20', '%2A', '~'];
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalURI($path)
{
if (empty($path)) return '/';
$pattens = explode('/', $path);
$pattens = array_map(function ($item) {
return $this->escape($item);
}, $pattens);
$canonicalURI = implode('/', $pattens);
return $canonicalURI;
}
private function getCanonicalQueryString($parameters)
{
@@ -252,6 +266,9 @@ class AWS
private function curl($method, $url, $body, $header, $xml = false, $etag = false)
{
$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, $header);

View File

@@ -13,13 +13,15 @@ class Aliyun
private $AccessKeySecret;
private $Endpoint;
private $Version;
private $proxy = false;
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint, $Version)
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint, $Version, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->AccessKeySecret = $AccessKeySecret;
$this->Endpoint = $Endpoint;
$this->Version = $Version;
$this->proxy = $proxy;
}
/**
@@ -45,6 +47,9 @@ class Aliyun
$url .= '?' . http_build_query($data);
}
$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_RETURNTRANSFER, true);

View File

@@ -13,13 +13,15 @@ class AliyunNew
private $AccessKeySecret;
private $Endpoint;
private $Version;
private $proxy = false;
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint, $Version)
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint, $Version, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->AccessKeySecret = $AccessKeySecret;
$this->Endpoint = $Endpoint;
$this->Version = $Version;
$this->proxy = $proxy;
}
/**
@@ -32,13 +34,15 @@ class AliyunNew
public function request($method, $action, $path = '/', $params = null)
{
if (!empty($params)) {
$params = array_filter($params, function ($a) { return $a !== null;});
$params = array_filter($params, function ($a) {
return $a !== null;
});
}
if($method == 'GET' || $method == 'DELETE'){
if ($method == 'GET' || $method == 'DELETE') {
$query = $params;
$body = '';
}else{
} else {
$query = [];
$body = !empty($params) ? json_encode($params) : '';
}
@@ -57,13 +61,13 @@ class AliyunNew
$authorization = $this->generateSign($method, $path, $query, $headers, $body);
$headers['Authorization'] = $authorization;
$url = 'https://'.$this->Endpoint.$path;
$url = 'https://' . $this->Endpoint . $path;
if (!empty($query)) {
$url .= '?'.http_build_query($query);
$url .= '?' . http_build_query($query);
}
$header = [];
foreach ($headers as $key => $value) {
$header[] = $key.': '.$value;
$header[] = $key . ': ' . $value;
}
return $this->curl($method, $url, $body, $header);
}
@@ -74,21 +78,21 @@ class AliyunNew
// step 1: build canonical request string
$httpRequestMethod = $method;
$canonicalUri = $path;
$canonicalUri = $this->getCanonicalURI($path);
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
$canonicalRequest = $httpRequestMethod."\n"
.$canonicalUri."\n"
.$canonicalQueryString."\n"
.$canonicalHeaders."\n"
.$signedHeaders."\n"
.$hashedRequestPayload;
$canonicalRequest = $httpRequestMethod . "\n"
. $canonicalUri . "\n"
. $canonicalQueryString . "\n"
. $canonicalHeaders . "\n"
. $signedHeaders . "\n"
. $hashedRequestPayload;
// step 2: build string to sign
$hashedCanonicalRequest = hash("sha256", $canonicalRequest);
$stringToSign = $algorithm."\n"
.$hashedCanonicalRequest;
$stringToSign = $algorithm . "\n"
. $hashedCanonicalRequest;
// step 3: sign string
$signature = hash_hmac("sha256", $stringToSign, $this->AccessKeySecret);
@@ -106,13 +110,24 @@ class AliyunNew
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalURI($path)
{
if (empty($path)) return '/';
$pattens = explode('/', $path);
$pattens = array_map(function ($item) {
return $this->escape($item);
}, $pattens);
$canonicalURI = implode('/', $pattens);
return $canonicalURI;
}
private function getCanonicalQueryString($parameters)
{
if (empty($parameters)) return '';
ksort($parameters);
$canonicalQueryString = '';
foreach ($parameters as $key => $value) {
$canonicalQueryString .= '&' . $this->escape($key). '=' . $this->escape($value);
$canonicalQueryString .= '&' . $this->escape($key) . '=' . $this->escape($value);
}
return substr($canonicalQueryString, 1);
}
@@ -138,6 +153,9 @@ class AliyunNew
private function curl($method, $url, $body, $header)
{
$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, $header);
@@ -160,10 +178,10 @@ class AliyunNew
if ($httpCode == 200) {
return $arr;
} elseif ($arr) {
if(strpos($arr['Message'], '.') > 0) $arr['Message'] = substr($arr['Message'], 0, strpos($arr['Message'], '.')+1);
if (strpos($arr['Message'], '.') > 0) $arr['Message'] = substr($arr['Message'], 0, strpos($arr['Message'], '.') + 1);
throw new Exception($arr['Message']);
} else {
throw new Exception('返回数据解析失败');
}
}
}
}

View File

@@ -9,12 +9,14 @@ class AliyunOSS
private $AccessKeyId;
private $AccessKeySecret;
private $Endpoint;
private $proxy = false;
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint)
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->AccessKeySecret = $AccessKeySecret;
$this->Endpoint = $Endpoint;
$this->proxy = $proxy;
}
public function addBucketCnameCert($bucket, $domain, $cert_id)
@@ -101,6 +103,9 @@ class AliyunOSS
private function curl($method, $url, $body, $header)
{
$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, $header);

View File

@@ -12,12 +12,14 @@ class BaiduCloud
private $AccessKeyId;
private $SecretAccessKey;
private $endpoint;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint)
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
$this->endpoint = $endpoint;
$this->proxy = $proxy;
}
/**
@@ -140,6 +142,9 @@ class BaiduCloud
private function curl($method, $url, $body, $header)
{
$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, $header);

163
app/lib/client/Ctyun.php Normal file
View File

@@ -0,0 +1,163 @@
<?php
namespace app\lib\client;
use Exception;
/**
* 天翼云
*/
class Ctyun
{
private $AccessKeyId;
private $SecretAccessKey;
private $endpoint;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
$this->endpoint = $endpoint;
$this->proxy = $proxy;
}
/**
* @param string $method 请求方法
* @param string $path 请求路径
* @param array|null $query 请求参数
* @param array|null $params 请求体
* @return array
* @throws Exception
*/
public function request($method, $path, $query = null, $params = null)
{
if (!empty($query)) {
$query = array_filter($query, function ($a) { return $a !== null;});
}
if (!empty($params)) {
$params = array_filter($params, function ($a) { return $a !== null;});
}
$time = time();
$date = date("Ymd\THis\Z", $time);
$body = !empty($params) ? json_encode($params) : '';
$headers = [
'Host' => $this->endpoint,
'Eop-date' => $date,
'ctyun-eop-request-id' => getSid(),
];
if ($body) {
$headers['Content-Type'] = 'application/json';
}
$authorization = $this->generateSign($query, $headers, $body, $date);
$headers['Eop-Authorization'] = $authorization;
$url = 'https://' . $this->endpoint . $path;
if (!empty($query)) {
$url .= '?' . http_build_query($query);
}
$header = [];
foreach ($headers as $key => $value) {
$header[] = $key . ': ' . $value;
}
return $this->curl($method, $url, $body, $header);
}
private function generateSign($query, $headers, $body, $date)
{
// step 1: build canonical request string
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
// step 2: build string to sign
$stringToSign = $canonicalHeaders . "\n"
. $canonicalQueryString . "\n"
. $hashedRequestPayload;
// step 3: sign string
$ktime = hash_hmac("sha256", $date, $this->SecretAccessKey, true);
$kAk = hash_hmac("sha256", $this->AccessKeyId, $ktime, true);
$kdate = hash_hmac("sha256", substr($date, 0, 8), $kAk, true);
$signature = hash_hmac("sha256", $stringToSign, $kdate, true);
$signature = base64_encode($signature);
// step 4: build authorization
$authorization = $this->AccessKeyId . " Headers=" . $signedHeaders . " Signature=" . $signature;
return $authorization;
}
private function escape($str)
{
$search = ['+', '*', '%7E'];
$replace = ['%20', '%2A', '~'];
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalQueryString($parameters)
{
if (empty($parameters)) return '';
ksort($parameters);
$canonicalQueryString = '';
foreach ($parameters as $key => $value) {
$canonicalQueryString .= '&' . $this->escape($key) . '=' . $this->escape($value);
}
return substr($canonicalQueryString, 1);
}
private function getCanonicalHeaders($oldheaders)
{
$headers = array();
foreach ($oldheaders as $key => $value) {
$headers[strtolower($key)] = trim($value);
}
ksort($headers);
$canonicalHeaders = '';
$signedHeaders = '';
foreach ($headers as $key => $value) {
$canonicalHeaders .= $key . ':' . $value . "\n";
$signedHeaders .= $key . ';';
}
$signedHeaders = substr($signedHeaders, 0, -1);
return [$canonicalHeaders, $signedHeaders];
}
private function curl($method, $url, $body, $header)
{
$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, $header);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if (!empty($body)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$errno = curl_errno($ch);
if ($errno) {
curl_close($ch);
throw new Exception('Curl error: ' . curl_error($ch));
}
curl_close($ch);
$arr = json_decode($response, true);
if (isset($arr['statusCode']) && $arr['statusCode'] == 100000) {
return isset($arr['returnObj']) ? $arr['returnObj'] : true;
} elseif (isset($arr['errorMessage'])) {
throw new Exception($arr['errorMessage']);
} elseif (isset($arr['message'])) {
throw new Exception($arr['message']);
} else {
throw new Exception('返回数据解析失败');
}
}
}

View File

@@ -12,12 +12,14 @@ class HuaweiCloud
private $AccessKeyId;
private $SecretAccessKey;
private $endpoint;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint)
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
$this->endpoint = $endpoint;
$this->proxy = $proxy;
}
/**
@@ -68,8 +70,7 @@ class HuaweiCloud
// step 1: build canonical request string
$httpRequestMethod = $method;
$canonicalUri = $path;
if (substr($canonicalUri, -1) != "/") $canonicalUri .= "/";
$canonicalUri = $this->getCanonicalURI($path);
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
@@ -103,6 +104,18 @@ class HuaweiCloud
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalURI($path)
{
if (empty($path)) return '/';
$pattens = explode('/', $path);
$pattens = array_map(function ($item) {
return $this->escape($item);
}, $pattens);
$canonicalURI = implode('/', $pattens);
if (substr($canonicalURI, -1) != '/') $canonicalURI .= '/';
return $canonicalURI;
}
private function getCanonicalQueryString($parameters)
{
if (empty($parameters)) return '';
@@ -135,6 +148,9 @@ class HuaweiCloud
private function curl($method, $url, $body, $header)
{
$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, $header);
@@ -150,6 +166,7 @@ class HuaweiCloud
curl_close($ch);
throw new Exception('Curl error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$arr = json_decode($response, true);
@@ -164,7 +181,6 @@ class HuaweiCloud
return $arr;
}
} else {
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode >= 200 && $httpCode < 300) {
return null;
} else {

190
app/lib/client/Jdcloud.php Normal file
View File

@@ -0,0 +1,190 @@
<?php
namespace app\lib\client;
use Exception;
/**
* 京东云
*/
class Jdcloud
{
private static $algorithm = 'JDCLOUD2-HMAC-SHA256';
private $AccessKeyId;
private $AccessKeySecret;
private $endpoint;
private $service;
private $region;
private $proxy = false;
public function __construct($AccessKeyId, $AccessKeySecret, $endpoint, $service, $region, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->AccessKeySecret = $AccessKeySecret;
$this->endpoint = $endpoint;
$this->service = $service;
$this->region = $region;
$this->proxy = $proxy;
}
/**
* @param string $method 请求方法
* @param string $path 请求路径
* @param array $params 请求参数
* @return array
* @throws Exception
*/
public function request($method, $path, $params = [])
{
if (!empty($params)) {
$params = array_filter($params, function ($a) {
return $a !== null;
});
}
if ($method == 'GET' || $method == 'DELETE') {
$query = $params;
$body = '';
} else {
$query = [];
$body = !empty($params) ? json_encode($params) : '';
}
$date = gmdate("Ymd\THis\Z");
$headers = [
'Host' => $this->endpoint,
'x-jdcloud-algorithm' => self::$algorithm,
'x-jdcloud-date' => $date,
'x-jdcloud-nonce' => uniqid('php', true),
];
if ($body) {
$headers['Content-Type'] = 'application/json';
}
$authorization = $this->generateSign($method, $path, $query, $headers, $body, $date);
$headers['authorization'] = $authorization;
$url = 'https://' . $this->endpoint . $path;
if (!empty($query)) {
$url .= '?' . http_build_query($query);
}
$header = [];
foreach ($headers as $key => $value) {
$header[] = $key . ': ' . $value;
}
return $this->curl($method, $url, $body, $header);
}
private function generateSign($method, $path, $query, $headers, $body, $date)
{
// step 1: build canonical request string
$httpRequestMethod = $method;
$canonicalUri = $path;
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
$canonicalRequest = $httpRequestMethod . "\n"
. $canonicalUri . "\n"
. $canonicalQueryString . "\n"
. $canonicalHeaders . "\n"
. $signedHeaders . "\n"
. $hashedRequestPayload;
// step 2: build string to sign
$shortDate = substr($date, 0, 8);
$credentialScope = $shortDate . '/' . $this->region . '/' . $this->service . '/jdcloud2_request';
$hashedCanonicalRequest = hash("sha256", $canonicalRequest);
$stringToSign = self::$algorithm . "\n"
. $date . "\n"
. $credentialScope . "\n"
. $hashedCanonicalRequest;
// step 3: sign string
$kDate = hash_hmac("sha256", $shortDate, 'JDCLOUD2' . $this->AccessKeySecret, true);
$kRegion = hash_hmac("sha256", $this->region, $kDate, true);
$kService = hash_hmac("sha256", $this->service, $kRegion, true);
$kSigning = hash_hmac("sha256", "jdcloud2_request", $kService, true);
$signature = hash_hmac("sha256", $stringToSign, $kSigning);
// step 4: build authorization
$credential = $this->AccessKeyId . '/' . $credentialScope;
$authorization = self::$algorithm . ' Credential=' . $credential . ", SignedHeaders=" . $signedHeaders . ", Signature=" . $signature;
return $authorization;
}
private function escape($str)
{
$search = ['+', '*', '%7E'];
$replace = ['%20', '%2A', '~'];
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalQueryString($parameters)
{
if (empty($parameters)) return '';
ksort($parameters);
$canonicalQueryString = '';
foreach ($parameters as $key => $value) {
$canonicalQueryString .= '&' . $this->escape($key) . '=' . $this->escape($value);
}
return substr($canonicalQueryString, 1);
}
private function getCanonicalHeaders($oldheaders)
{
$headers = array();
foreach ($oldheaders as $key => $value) {
$headers[strtolower($key)] = trim($value);
}
ksort($headers);
$canonicalHeaders = '';
$signedHeaders = '';
foreach ($headers as $key => $value) {
$canonicalHeaders .= $key . ':' . $value . "\n";
$signedHeaders .= $key . ';';
}
$signedHeaders = substr($signedHeaders, 0, -1);
return [$canonicalHeaders, $signedHeaders];
}
private function curl($method, $url, $body, $header)
{
$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, $header);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if (!empty($body)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$errno = curl_errno($ch);
if ($errno) {
curl_close($ch);
throw new Exception('Curl error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$arr = json_decode($response, true);
if ($httpCode == 200) {
if (isset($arr['result'])) {
return $arr['result'];
}
return $arr;
} else {
if (isset($arr['error']['message'])) {
throw new Exception($arr['error']['message']);
} else {
throw new Exception('返回数据解析失败(http_code=' . $httpCode . ')');
}
}
}
}

View File

@@ -12,11 +12,13 @@ class Qiniu
private $ApiUrl = 'https://api.qiniu.com';
private $AccessKey;
private $SecretKey;
private $proxy = false;
public function __construct($AccessKey, $SecretKey)
public function __construct($AccessKey, $SecretKey, $proxy = false)
{
$this->AccessKey = $AccessKey;
$this->SecretKey = $SecretKey;
$this->proxy = $proxy;
}
/**
@@ -59,6 +61,39 @@ class Qiniu
return $this->curl($method, $url, $body, $header);
}
public function pili_request($method, $path, $query = null, $params = null)
{
$this->ApiUrl = 'https://pili.qiniuapi.com';
$url = $this->ApiUrl . $path;
$query_str = null;
$body = null;
if (!empty($query)) {
$query = array_filter($query, function ($a) {
return $a !== null;
});
$query_str = http_build_query($query);
$url .= '?' . $query_str;
}
if (!empty($params)) {
$params = array_filter($params, function ($a) {
return $a !== null;
});
$body = json_encode($params);
}
$sign_str = $method . ' ' . $path . ($query_str ? '?' . $query_str : '') . "\nHost: pili.qiniuapi.com" . ($body ? "\nContent-Type: application/json" : '') . "\n\n" . $body;
$hmac = hash_hmac('sha1', $sign_str, $this->SecretKey, true);
$sign = $this->AccessKey . ':' . $this->base64_urlSafeEncode($hmac);
$header = [
'Authorization: Qiniu ' . $sign,
];
if ($body) {
$header[] = 'Content-Type: application/json';
}
return $this->curl($method, $url, $body, $header);
}
private function base64_urlSafeEncode($data)
{
$find = array('+', '/');
@@ -69,6 +104,9 @@ class Qiniu
private function curl($method, $url, $body, $header)
{
$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, $header);
@@ -90,7 +128,7 @@ class Qiniu
if ($httpCode == 200) {
$arr = json_decode($response, true);
if($arr) return $arr;
if ($arr) return $arr;
return true;
} else {
$arr = json_decode($response, true);

View File

@@ -15,8 +15,9 @@ class TencentCloud
private $service;
private $version;
private $region;
private $proxy = false;
public function __construct($SecretId, $SecretKey, $endpoint, $service, $version, $region = null)
public function __construct($SecretId, $SecretKey, $endpoint, $service, $version, $region = null, $proxy = false)
{
$this->SecretId = $SecretId;
$this->SecretKey = $SecretKey;
@@ -24,6 +25,7 @@ class TencentCloud
$this->service = $service;
$this->version = $version;
$this->region = $region;
$this->proxy = $proxy;
}
/**
@@ -98,6 +100,9 @@ class TencentCloud
{
$url = 'https://'.$this->endpoint.'/';
$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, $header);

View File

@@ -15,8 +15,9 @@ class Volcengine
private $service;
private $version;
private $region;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $service, $version, $region)
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $service, $version, $region, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
@@ -24,6 +25,7 @@ class Volcengine
$this->service = $service;
$this->version = $version;
$this->region = $region;
$this->proxy = $proxy;
}
/**
@@ -36,7 +38,9 @@ class Volcengine
public function request($method, $action, $params = [], $querys = [])
{
if (!empty($params)) {
$params = array_filter($params, function ($a) { return $a !== null;});
$params = array_filter($params, function ($a) {
return $a !== null;
});
}
$query = [
@@ -76,9 +80,51 @@ class Volcengine
return $this->curl($method, $url, $body, $header);
}
/**
* @param string $method 请求方法
* @param string $action 方法名称
* @param array $params 请求参数
* @return array
* @throws Exception
*/
public function tos_request($method, $params = [], $query = [])
{
if (!empty($params)) {
$params = array_filter($params, function ($a) {
return $a !== null;
});
}
$body = '';
if ($method != 'GET') {
$body = !empty($params) ? json_encode($params) : '';
}
$time = time();
$headers = [
'Host' => $this->endpoint,
'X-Tos-Date' => gmdate("Ymd\THis\Z", $time),
'X-Tos-Content-Sha256' => hash("sha256", $body),
];
if ($body) {
$headers['Content-Type'] = 'application/json';
}
$path = '/';
$authorization = $this->generateSign($method, $path, $query, $headers, $body, $time);
$headers['Authorization'] = $authorization;
$url = 'https://' . $this->endpoint . $path . '?' . http_build_query($query);
$header = [];
foreach ($headers as $key => $value) {
$header[] = $key . ': ' . $value;
}
return $this->curl($method, $url, $body, $header);
}
private function generateSign($method, $path, $query, $headers, $body, $time)
{
$algorithm = "HMAC-SHA256";
$algorithm = $this->service == 'tos' ? "TOS4-HMAC-SHA256" : "HMAC-SHA256";
// step 1: build canonical request string
$httpRequestMethod = $method;
@@ -157,6 +203,9 @@ class Volcengine
private function curl($method, $url, $body, $header)
{
$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, $header);
@@ -172,21 +221,31 @@ class Volcengine
curl_close($ch);
throw new Exception('Curl error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$arr = json_decode($response, true);
if ($arr) {
if ($httpCode == 200) {
if (isset($arr['ResponseMetadata']['Error']['MessageCN'])) {
throw new Exception($arr['ResponseMetadata']['Error']['MessageCN']);
} elseif (isset($arr['ResponseMetadata']['Error']['Message'])) {
throw new Exception($arr['ResponseMetadata']['Error']['Message']);
} elseif (isset($arr['Result'])) {
return $arr['Result'];
} else {
return true;
}
return true;
} else {
throw new Exception('返回数据解析失败');
if (isset($arr['ResponseMetadata']['Error']['MessageCN'])) {
throw new Exception($arr['ResponseMetadata']['Error']['MessageCN']);
} elseif (isset($arr['ResponseMetadata']['Error']['Message'])) {
throw new Exception($arr['ResponseMetadata']['Error']['Message']);
} elseif (isset($arr['Message'])) {
throw new Exception($arr['Message']);
} elseif (isset($arr['message'])) {
throw new Exception($arr['message']);
} else {
throw new Exception('返回数据解析失败(http_code=' . $httpCode . ')');
}
}
}
}

View File

@@ -13,17 +13,19 @@ class aliyun implements DeployInterface
private $logger;
private $AccessKeyId;
private $AccessKeySecret;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->AccessKeySecret = $config['AccessKeySecret'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->AccessKeySecret)) throw new Exception('必填参数不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cas.aliyuncs.com', '2020-04-07');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cas.aliyuncs.com', '2020-04-07', $this->proxy);
$param = ['Action' => 'ListUserCertificateOrder'];
$client->request($param);
return true;
@@ -79,11 +81,11 @@ class aliyun implements DeployInterface
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$serial_no = strtolower($certInfo['serialNumberHex']);
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cas.aliyuncs.com', '2020-04-07');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cas.aliyuncs.com', '2020-04-07', $this->proxy);
$param = [
'Action' => 'ListUserCertificateOrder',
'Keyword' => $certInfo['subject']['CN'],
'OrderType' => 'UPLOAD',
'OrderType' => 'CERT',
];
try {
$data = $client->request($param);
@@ -125,7 +127,7 @@ class aliyun implements DeployInterface
{
$domain = $config['domain'];
if (empty($domain)) throw new Exception('CDN绑定域名不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cdn.aliyuncs.com', '2018-05-10');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cdn.aliyuncs.com', '2018-05-10', $this->proxy);
$param = [
'Action' => 'SetCdnDomainSSLCertificate',
'DomainName' => $domain,
@@ -142,7 +144,7 @@ class aliyun implements DeployInterface
{
$domain = $config['domain'];
if (empty($domain)) throw new Exception('DCDN绑定域名不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'dcdn.aliyuncs.com', '2018-01-15');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'dcdn.aliyuncs.com', '2018-01-15', $this->proxy);
$param = [
'Action' => 'SetDcdnDomainSSLCertificate',
'DomainName' => $domain,
@@ -188,6 +190,7 @@ class aliyun implements DeployInterface
$cert_id = null;
$cert_name = null;
$casid = null;
foreach ($data['Result'] as $cert) {
$domains = explode(',', $cert['SAN']);
$flag = true;
@@ -200,6 +203,7 @@ class aliyun implements DeployInterface
if ($flag) {
$cert_id = $cert['Id'];
$cert_name = $cert['CommonName'];
$casid = $cert['CasId'];
break;
}
}
@@ -213,6 +217,10 @@ class aliyun implements DeployInterface
if ($cert_id) {
$param['Update'] = 'true';
$param['Id'] = $cert_id;
if ($casid == $cas_id) {
$this->log('ESA站点 ' . $sitename . ' 证书已配置,无需重复操作');
return;
}
}
$client->request($param);
if ($cert_id) {
@@ -239,7 +247,7 @@ class aliyun implements DeployInterface
$endpoint = 'wafopenapi.' . $config['region'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2021-10-01');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2021-10-01', $this->proxy);
$param = [
'Action' => 'DescribeInstance',
@@ -298,7 +306,7 @@ class aliyun implements DeployInterface
$endpoint = 'wafopenapi.' . $config['region'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2019-09-10');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2019-09-10', $this->proxy);
$param = [
'Action' => 'DescribeInstanceInfo',
@@ -337,7 +345,7 @@ class aliyun implements DeployInterface
$endpoint = 'apigateway.' . $config['regionid'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2016-07-14');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2016-07-14', $this->proxy);
$param = [
'Action' => 'SetDomainCertificate',
@@ -359,7 +367,7 @@ class aliyun implements DeployInterface
$endpoint = 'ddoscoo.' . $config['region'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-01-01');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-01-01', $this->proxy);
$param = [
'Action' => 'AssociateWebCert',
@@ -375,7 +383,7 @@ class aliyun implements DeployInterface
{
$domain = $config['domain'];
if (empty($domain)) throw new Exception('视频直播绑定域名不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'live.aliyuncs.com', '2016-11-01');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'live.aliyuncs.com', '2016-11-01', $this->proxy);
$param = [
'Action' => 'SetLiveDomainCertificate',
'DomainName' => $domain,
@@ -392,7 +400,7 @@ class aliyun implements DeployInterface
{
$domain = $config['domain'];
if (empty($domain)) throw new Exception('视频点播绑定域名不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'vod.cn-shanghai.aliyuncs.com', '2017-03-21');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'vod.cn-shanghai.aliyuncs.com', '2017-03-21', $this->proxy);
$param = [
'Action' => 'SetVodDomainCertificate',
'DomainName' => $domain,
@@ -415,7 +423,7 @@ class aliyun implements DeployInterface
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new AliyunNewClient($this->AccessKeyId, $this->AccessKeySecret, $fc_cname, '2023-03-30');
$client = new AliyunNewClient($this->AccessKeyId, $this->AccessKeySecret, $fc_cname, '2023-03-30', $this->proxy);
try {
$data = $client->request('GET', 'GetCustomDomain', '/2023-03-30/custom-domains/' . $domain);
@@ -458,7 +466,7 @@ class aliyun implements DeployInterface
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new AliyunNewClient($this->AccessKeyId, $this->AccessKeySecret, $fc_cname, '2021-04-06');
$client = new AliyunNewClient($this->AccessKeyId, $this->AccessKeySecret, $fc_cname, '2021-04-06', $this->proxy);
try {
$data = $client->request('GET', 'GetCustomDomain', '/2021-04-06/custom-domains/' . $domain);
@@ -495,7 +503,7 @@ class aliyun implements DeployInterface
if (empty($config['clb_port'])) throw new Exception('HTTPS监听端口不能为空');
$endpoint = 'slb.' . $config['regionid'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2014-05-15');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2014-05-15', $this->proxy);
$param = [
'Action' => 'DescribeServerCertificates',
@@ -570,7 +578,7 @@ class aliyun implements DeployInterface
if (empty($config['alb_listener_id'])) throw new Exception('负载均衡监听ID不能为空');
$endpoint = 'alb.' . $config['regionid'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-06-16');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-06-16', $this->proxy);
$param = [
'Action' => 'ListListenerCertificates',
@@ -605,7 +613,7 @@ class aliyun implements DeployInterface
if (empty($config['nlb_listener_id'])) throw new Exception('负载均衡监听ID不能为空');
$endpoint = 'nlb.' . $config['regionid'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2022-04-30');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2022-04-30', $this->proxy);
$param = [
'Action' => 'ListListenerCertificates',

View File

@@ -52,14 +52,33 @@ class allwaf implements DeployInterface
$this->log('获取证书列表成功(total=' . count($list) . ')');
$certInfo = openssl_x509_parse($fullchain, true);
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
foreach ($list as $row) {
if (!empty($list)) {
foreach ($list as $row) {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
'timeBeginAt' => $certInfo['validFrom_time_t'],
'timeEndAt' => $certInfo['validTo_time_t'],
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
}
} else {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'name' => $cert_name,
'description' => $cert_name,
'serverName' => $certInfo['subject']['CN'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
@@ -68,8 +87,8 @@ class allwaf implements DeployInterface
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
$result = $this->request('/SSLCertService/createSSLCert', $params);
$this->log('证书ID:' . $result['sslCertId'] . '添加成功!');
}
}

View File

@@ -11,17 +11,19 @@ class aws implements DeployInterface
private $logger;
private $AccessKeyId;
private $SecretAccessKey;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'iam.amazonaws.com', 'iam', '2010-05-08', 'us-east-1');
$client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'iam.amazonaws.com', 'iam', '2010-05-08', 'us-east-1', $this->proxy);
$client->requestXml('GET', 'GetUser');
return true;
}
@@ -44,7 +46,7 @@ class aws implements DeployInterface
usleep(500000);
}
$client = new \app\lib\client\AWS($this->AccessKeyId, $this->SecretAccessKey, 'cloudfront.amazonaws.com', 'cloudfront', '2020-05-31', 'us-east-1');
$client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'cloudfront.amazonaws.com', 'cloudfront', '2020-05-31', 'us-east-1', $this->proxy);
try {
$data = $client->requestXmlN('GET', '/distribution/' . $config['distribution_id'] . '/config', [], null, true);
} catch (Exception $e) {
@@ -66,7 +68,7 @@ class aws implements DeployInterface
'PrivateKey' => base64_encode($privatekey),
];
$client = new \app\lib\client\AWS($this->AccessKeyId, $this->SecretAccessKey, 'acm.us-east-1.amazonaws.com', 'acm', '', 'us-east-1');
$client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'acm.us-east-1.amazonaws.com', 'acm', '', 'us-east-1', $this->proxy);
try {
$data = $client->request('POST', 'CertificateManager.ImportCertificate', $param);
$cert_id = $data['CertificateArn'];

View File

@@ -11,17 +11,19 @@ class baidu implements DeployInterface
private $logger;
private $AccessKeyId;
private $SecretAccessKey;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.baidubce.com');
$client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.baidubce.com', $this->proxy);
$client->request('GET', '/v2/domain');
return true;
}
@@ -33,7 +35,7 @@ class baidu implements DeployInterface
if (!$certInfo) throw new Exception('证书解析失败');
$config['cert_name'] = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.baidubce.com');
$client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.baidubce.com', $this->proxy);
try {
$data = $client->request('GET', '/v2/' . $config['domain'] . '/certificates');
if (isset($data['certName']) && $data['certName'] == $config['cert_name']) {

View File

@@ -26,7 +26,7 @@ class btpanel implements DeployInterface
$path = '/config?action=get_config';
$response = $this->request($path, []);
$result = json_decode($response, true);
if (isset($result['status']) && $result['status'] == 1) {
if (isset($result['status']) && ($result['status']==1 || isset($result['sites_path']))) {
return true;
} else {
throw new Exception(isset($result['msg']) ? $result['msg'] : '面板地址无法连接');
@@ -46,13 +46,24 @@ class btpanel implements DeployInterface
foreach ($sites as $site) {
$siteName = trim($site);
if (empty($siteName)) continue;
try {
$this->deploySite($siteName, $fullchain, $privatekey);
$this->log("网站 {$siteName} 证书部署成功");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("网站 {$siteName} 证书部署失败:" . $errmsg);
if ($config['type'] == '2') {
try {
$this->deployMailSys($siteName, $fullchain, $privatekey);
$this->log("邮局域名 {$siteName} 证书部署成功");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("邮局域名 {$siteName} 证书部署失败:" . $errmsg);
}
} else {
try {
$this->deploySite($siteName, $fullchain, $privatekey);
$this->log("网站 {$siteName} 证书部署成功");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("网站 {$siteName} 证书部署失败:" . $errmsg);
}
}
}
if ($success == 0) {
@@ -98,6 +109,26 @@ class btpanel implements DeployInterface
}
}
private function deployMailSys($domain, $fullchain, $privatekey)
{
$path = '/plugin?action=a&name=mail_sys&s=set_mail_certificate_multiple';
$data = [
'domain' => $domain,
'key' => $privatekey,
'csr' => $fullchain,
'act' => 'add',
];
$response = $this->request($path, $data);
$result = json_decode($response, true);
if (isset($result['status']) && $result['status']) {
return true;
} elseif (isset($result['msg'])) {
throw new Exception($result['msg']);
} else {
throw new Exception($response ? $response : '返回数据解析失败');
}
}
public function setLogger($func)
{
$this->logger = $func;

184
app/lib/deploy/ctyun.php Normal file
View File

@@ -0,0 +1,184 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use app\lib\client\Ctyun as CtyunClient;
use Exception;
class ctyun implements DeployInterface
{
private $logger;
private $AccessKeyId;
private $SecretAccessKey;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'ctcdn-global.ctapi.ctyun.cn', $this->proxy);
$client->request('GET', '/v1/cert/query-cert-list');
return true;
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$config['cert_name'] = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
if ($config['product'] == 'cdn') {
$this->deploy_cdn($fullchain, $privatekey, $config);
} elseif ($config['product'] == 'icdn') {
$this->deploy_icdn($fullchain, $privatekey, $config);
} elseif ($config['product'] == 'accessone') {
$this->deploy_accessone($fullchain, $privatekey, $config);
}
}
private function deploy_cdn($fullchain, $privatekey, $config)
{
$client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'ctcdn-global.ctapi.ctyun.cn', $this->proxy);
$param = [
'name' => $config['cert_name'],
'key' => $privatekey,
'certs' => $fullchain,
];
try {
$client->request('POST', '/v1/cert/creat-cert', null, $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '已存在重名的证书') !== false) {
$this->log('已存在重名的证书 cert_name=' . $config['cert_name']);
} else {
throw new Exception('上传证书失败:' . $e->getMessage());
}
}
$this->log('上传证书成功 cert_name=' . $config['cert_name']);
$param = [
'domain' => $config['domain'],
'https_status' => 'on',
'cert_name' => $config['cert_name'],
];
try {
$client->request('POST', '/v1/domain/update-domain', null, $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '请求已提交,请勿重复操作!') === false) {
throw new Exception($e->getMessage());
}
}
$this->log('CDN域名 ' . $config['domain'] . ' 部署证书成功!');
}
private function deploy_icdn($fullchain, $privatekey, $config)
{
$client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'icdn-global.ctapi.ctyun.cn', $this->proxy);
$param = [
'name' => $config['cert_name'],
'key' => $privatekey,
'certs' => $fullchain,
];
try {
$client->request('POST', '/v1/cert/creat-cert', null, $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '已存在重名的证书') !== false) {
$this->log('已存在重名的证书 cert_name=' . $config['cert_name']);
} else {
throw new Exception('上传证书失败:' . $e->getMessage());
}
}
$this->log('上传证书成功 cert_name=' . $config['cert_name']);
$param = [
'domain' => $config['domain'],
'https_status' => 'on',
'cert_name' => $config['cert_name'],
];
try {
$client->request('POST', '/v1/domain/update-domain', null, $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '请求已提交,请勿重复操作!') === false) {
throw new Exception($e->getMessage());
}
}
$this->log('CDN域名 ' . $config['domain'] . ' 部署证书成功!');
}
private function deploy_accessone($fullchain, $privatekey, $config)
{
$client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'accessone-global.ctapi.ctyun.cn', $this->proxy);
$param = [
'name' => $config['cert_name'],
'key' => $privatekey,
'certs' => $fullchain,
];
try {
$client->request('POST', '/ctapi/v1/accessone/cert/create', null, $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '已存在重名的证书') !== false) {
$this->log('已存在重名的证书 cert_name=' . $config['cert_name']);
} else {
throw new Exception('上传证书失败:' . $e->getMessage());
}
}
$this->log('上传证书成功 cert_name=' . $config['cert_name']);
$param = [
'domain' => $config['domain'],
'product_code' => '020',
];
try {
$result = $client->request('POST', '/ctapi/v1/accessone/domain/config', null, $param);
} catch (Exception $e) {
throw new Exception('查询域名配置失败:' . $e->getMessage());
}
if ($result['https_status'] == 'on' && $result['cert_name'] == $config['cert_name']) {
$this->log('边缘安全加速域名 ' . $config['domain'] . ' 证书已部署,无需重复操作!');
return;
}
$result['https_status'] = 'on';
$result['cert_name'] = $config['cert_name'];
$exclude_keys = ['status', 'area_scope', 'cname', 'insert_date', 'status_date', 'record_status', 'record_num', 'customer_name', 'outlink_replace_filter', 'website_ipv6_access_mark', 'websocket_speed', 'dynamic_config', 'dynamic_ability'];
foreach ($result as $key => $value) {
if (in_array($key, $exclude_keys) || is_array($value) && empty($value)) {
unset($result[$key]);
}
}
if (isset($result['origin'])) {
foreach ($result['origin'] as &$origin) {
$origin['weight'] = strval($origin['weight']);
}
}
try {
$client->request('POST', '/ctapi/v1/accessone/domain/modify_config', null, $result);
} catch (Exception $e) {
if (strpos($e->getMessage(), '请求已提交,请勿重复操作!') === false) {
throw new Exception($e->getMessage());
}
}
$this->log('边缘安全加速域名 ' . $config['domain'] . ' 部署证书成功!');
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

View File

@@ -10,11 +10,13 @@ class doge implements DeployInterface
private $logger;
private $AccessKey;
private $SecretKey;
private $proxy;
public function __construct($config)
{
$this->AccessKey = $config['AccessKey'];
$this->SecretKey = $config['SecretKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
@@ -95,7 +97,7 @@ class doge implements DeployInterface
$headers = ['Authorization: ' . $authorization];
if($body && $json) $headers[] = 'Content-Type: application/json';
$url = 'https://api.dogecloud.com'.$path;
$response = curl_client($url, $body, null, null, $headers);
$response = curl_client($url, $body, null, null, $headers, $this->proxy);
$result = json_decode($response['body'], true);
if(isset($result['code']) && $result['code'] == 200){
return isset($result['data']) ? $result['data'] : true;

View File

@@ -50,20 +50,39 @@ class goedge implements DeployInterface
throw new Exception('获取证书列表失败:' . $e->getMessage());
}
$list = json_decode(base64_decode($data['sslCertsJSON']), true);
if (!$list || empty($list)) {
if ($list === false) {
throw new Exception('证书列表为空');
}
$this->log('获取证书列表成功(total=' . count($list) . ')');
$certInfo = openssl_x509_parse($fullchain, true);
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
foreach ($list as $row) {
if (!empty($list)) {
foreach ($list as $row) {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
'timeBeginAt' => $certInfo['validFrom_time_t'],
'timeEndAt' => $certInfo['validTo_time_t'],
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
}
} else {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'name' => $cert_name,
'description' => $cert_name,
'serverName' => $certInfo['subject']['CN'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
@@ -72,8 +91,8 @@ class goedge implements DeployInterface
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
$result = $this->request('/SSLCertService/createSSLCert', $params);
$this->log('证书ID:' . $result['sslCertId'] . '添加成功!');
}
}

View File

@@ -11,17 +11,19 @@ class huawei implements DeployInterface
private $logger;
private $AccessKeyId;
private $SecretAccessKey;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'scm.cn-north-4.myhuaweicloud.com');
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'scm.cn-north-4.myhuaweicloud.com', $this->proxy);
$client->request('GET', '/v3/scm/certificates');
return true;
}
@@ -43,7 +45,7 @@ class huawei implements DeployInterface
private function deploy_cdn($fullchain, $privatekey, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.myhuaweicloud.com');
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.myhuaweicloud.com', $this->proxy);
$param = [
'configs' => [
'https' => [
@@ -56,8 +58,10 @@ class huawei implements DeployInterface
],
],
];
$client->request('PUT', '/v1.1/cdn/configuration/domains/' . $config['domain'] . '/configs', null, $param);
$this->log('CDN域名 ' . $config['domain'] . ' 部署证书成功!');
foreach (explode(',', $config['domain']) as $domain) {
$client->request('PUT', '/v1.1/cdn/configuration/domains/' . $domain . '/configs', null, $param);
$this->log('CDN域名 ' . $domain . ' 部署证书成功!');
}
}
private function deploy_elb($fullchain, $privatekey, $config)
@@ -66,7 +70,7 @@ class huawei implements DeployInterface
if (empty($config['region_id'])) throw new Exception('区域ID不能为空');
if (empty($config['cert_id'])) throw new Exception('证书ID不能为空');
$endpoint = 'elb.' . $config['region_id'] . '.myhuaweicloud.com';
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $endpoint);
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $endpoint, $this->proxy);
try {
$data = $client->request('GET', '/v3/' . $config['project_id'] . '/elb/certificates/' . $config['cert_id']);
} catch (Exception $e) {
@@ -93,7 +97,7 @@ class huawei implements DeployInterface
if (empty($config['region_id'])) throw new Exception('区域ID不能为空');
if (empty($config['cert_id'])) throw new Exception('证书ID不能为空');
$endpoint = 'waf.' . $config['region_id'] . '.myhuaweicloud.com';
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $endpoint);
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $endpoint, $this->proxy);
try {
$data = $client->request('GET', '/v1/' . $config['project_id'] . '/waf/certificates/' . $config['cert_id']);
} catch (Exception $e) {
@@ -118,7 +122,7 @@ class huawei implements DeployInterface
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'scm.cn-north-4.myhuaweicloud.com');
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'scm.cn-north-4.myhuaweicloud.com', $this->proxy);
$param = [
'name' => $cert_name,
'certificate' => $fullchain,

View File

@@ -11,34 +11,51 @@ class huoshan implements DeployInterface
private $logger;
private $AccessKeyId;
private $SecretAccessKey;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'cdn.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1', $this->proxy);
$client->request('POST', 'ListCertInfo', ['Source' => 'volc_cert_center']);
return true;
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$cert_id = $this->get_cert_id($fullchain, $privatekey);
if (!$cert_id) throw new Exception('获取证书ID失败');
$info['cert_id'] = $cert_id;
$this->deploy_cdn($cert_id, $config);
if ($config['product'] == 'live') {
$this->deploy_live($fullchain, $privatekey, $config);
} else {
$cert_id = $this->get_cert_id($fullchain, $privatekey);
if (!$cert_id) throw new Exception('获取证书ID失败');
$info['cert_id'] = $cert_id;
if (!isset($config['product']) || $config['product'] == 'cdn') {
$this->deploy_cdn($cert_id, $config);
} elseif ($config['product'] == 'dcdn') {
$this->deploy_dcdn($cert_id, $config);
} elseif ($config['product'] == 'tos') {
$this->deploy_tos($cert_id, $config);
} elseif ($config['product'] == 'imagex') {
$this->deploy_imagex($cert_id, $config);
} elseif ($config['product'] == 'clb') {
$this->deploy_clb($cert_id, $config);
} elseif ($config['product'] == 'alb') {
$this->deploy_alb($cert_id, $config);
}
}
}
private function deploy_cdn($cert_id, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'cdn.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'cdn.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1', $this->proxy);
$param = [
'CertId' => $cert_id,
'Domain' => $config['domain'],
@@ -49,37 +66,149 @@ class huoshan implements DeployInterface
if ($row['Status'] == 'success') {
$this->log('CDN域名 ' . $row['Domain'] . ' 部署证书成功!');
} else {
$this->log('CDN域名 ' . $row['Domain'] . ' 部署证书失败:' . isset($row['ErrorMsg']) ? $row['ErrorMsg'] : '');
$this->log('CDN域名 ' . $row['Domain'] . ' 部署证书失败:' . (isset($row['ErrorMsg']) ? $row['ErrorMsg'] : ''));
}
}
}
private function deploy_dcdn($cert_id, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'dcdn', '2021-04-01', 'cn-north-1', $this->proxy);
$param = [
'CertId' => $cert_id,
'DomainNames' => explode(',', $config['domain']),
];
$client->request('POST', 'CreateCertBind', $param);
$this->log('DCDN域名 ' . $config['domain'] . ' 部署证书成功!');
}
private function deploy_tos($cert_id, $config)
{
if (empty($config['bucket_domain'])) throw new Exception('Bucket域名不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $config['bucket_domain'], 'tos', '2021-04-01', 'cn-beijing', $this->proxy);
foreach (explode(',', $config['domain']) as $domain) {
$param = [
'CustomDomainRule' => [
'Domain' => $domain,
'CertId' => $cert_id,
]
];
$query = ['customdomain' => ''];
$client->tos_request('PUT', $param, $query);
$this->log('对象存储域名 ' . $config['domain'] . ' 部署证书成功!');
}
}
private function deploy_live($fullchain, $privatekey, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'live.volcengineapi.com', 'live', '2023-01-01', 'cn-north-1', $this->proxy);
$param = [
'CertName' => $cert_name,
'Rsa' => [
'Pubkey' => $fullchain,
'Prikey' => $privatekey,
],
'UseWay' => 'https',
];
$result = $client->request('POST', 'CreateCert', $param);
$this->log('上传证书成功 ChainID=' . $result['ChainID']);
foreach (explode(',', $config['domain']) as $domain) {
$param = [
'ChainID' => $result['ChainID'],
'Domain' => $domain,
'HTTPS' => true,
'HTTP2' => true,
];
$client->request('POST', 'BindCert', $param);
$this->log('视频直播域名 ' . $domain . ' 部署证书成功!');
}
}
private function deploy_imagex($cert_id, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'imagex.volcengineapi.com', 'imagex', '2018-08-01', 'cn-north-1', $this->proxy);
foreach (explode(',', $config['domain']) as $domain) {
$param = [
[
'domain' => $domain,
'cert_id' => $cert_id,
]
];
$result = $client->request('POST', 'UpdateImageBatchDomainCert', $param);
if (isset($result['SuccessDomains']) && count($result['SuccessDomains']) > 0) {
$this->log('veImageX域名 ' . $domain . ' 部署证书成功!');
} elseif (isset($result['FailedDomains']) && count($result['FailedDomains']) > 0) {
$errmsg = $result['FailedDomains'][0]['ErrMsg'];
$this->log('veImageX域名 ' . $domain . ' 部署证书失败:' . $errmsg);
} else {
$this->log('veImageX域名 ' . $domain . ' 部署证书失败');
}
}
}
private function deploy_clb($cert_id, $config)
{
if (empty($config['listener_id'])) throw new Exception('监听器ID不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'clb', '2020-04-01', 'cn-beijing', $this->proxy);
$param = [
'ListenerId' => $config['listener_id'],
'CertificateSource' => 'cert_center',
'CertCenterCertificateId' => $cert_id,
];
$client->request('GET', 'ModifyListenerAttributes', $param);
$this->log('CLB监听器 ' . $config['listener_id'] . ' 部署证书成功!');
}
private function deploy_alb($cert_id, $config)
{
if (empty($config['listener_id'])) throw new Exception('监听器ID不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'alb', '2020-04-01', 'cn-beijing', $this->proxy);
$param = [
'ListenerId' => $config['listener_id'],
'CertificateSource' => 'cert_center',
'CertCenterCertificateId' => $cert_id,
];
$client->request('GET', 'ModifyListenerAttributes', $param);
$this->log('ALB监听器 ' . $config['listener_id'] . ' 部署证书成功!');
}
private function get_cert_id($fullchain, $privatekey)
{
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'cdn.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'certificate_service', '2024-10-01', 'cn-beijing', $this->proxy);
$param = [
'Source' => 'volc_cert_center',
'Certificate' => $fullchain,
'PrivateKey' => $privatekey,
'Desc' => $cert_name,
'Tag' => $cert_name,
'Repeatable' => false,
'CertificateInfo' => [
'CertificateChain' => $fullchain,
'PrivateKey' => $privatekey,
],
];
try {
$data = $client->request('POST', 'AddCertificate', $param);
$data = $client->request('POST', 'ImportCertificate', $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '证书已存在ID为') !== false) {
$cert_id = trim(getSubstr($e->getMessage(), '证书已存在ID为', '。'));
$this->log('证书已存在 CertId=' . $cert_id);
return $cert_id;
}
throw new Exception('上传证书失败:' . $e->getMessage());
}
$this->log('上传证书成功 CertId=' . $data['CertId']);
return $data['CertId'];
if (!empty($data['InstanceId'])) {
$cert_id = $data['InstanceId'];
} else {
$cert_id = $data['RepeatId'];
}
$this->log('上传证书成功 CertId=' . $cert_id);
return $cert_id;
}
public function setLogger($func)

View File

@@ -0,0 +1,173 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class kangleadmin implements DeployInterface
{
private $logger;
private $url;
private $path;
private $username;
private $skey;
private $proxy;
private $cookie;
public function __construct($config)
{
$this->url = rtrim($config['url'], '/');
if (empty($config['path'])) $config['path'] = '/admin';
$this->path = rtrim($config['path'], '/');
$this->username = $config['username'];
$this->skey = $config['skey'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->url) || empty($this->username) || empty($this->skey)) throw new Exception('必填参数不能为空');
$this->login();
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
if (empty($config['name'])) throw new Exception('网站用户名不能为空');
$this->login();
$this->log('登录成功 cookie:' . $this->cookie);
$this->loginVhost($config['name']);
if ($config['type'] == '1' && !empty($config['domains'])) {
$domains = explode("\n", $config['domains']);
$success = 0;
$errmsg = null;
foreach ($domains as $domain) {
$domain = trim($domain);
if (empty($domain)) continue;
try {
$this->deployDomain($domain, $fullchain, $privatekey);
$this->log("域名 {$domain} 证书部署成功");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("域名 {$domain} 证书部署失败:" . $errmsg);
}
}
if ($success == 0) {
throw new Exception($errmsg ? $errmsg : '要部署的域名不存在');
}
} else {
$this->deployAccount($fullchain, $privatekey);
$this->log("账号级SSL证书部署成功");
}
}
private function deployDomain($domain, $fullchain, $privatekey)
{
$path = '/vhost/?c=ssl&a=domainSsl';
$post = [
'domain' => $domain,
'certificate' => $fullchain,
'certificate_key' => $privatekey,
];
$response = curl_client($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy);
if (strpos($response['body'], '成功')) {
return true;
} elseif (preg_match('/alert\(\'(.*?)\'\)/i', $response['body'], $match)) {
throw new Exception(htmlspecialchars($match[1]));
} elseif (strlen($response['body']) > 3 && strlen($response['body']) < 50) {
throw new Exception(htmlspecialchars($response['body']));
} else {
throw new Exception('原因未知(httpCode=' . $response['code'] . ')');
}
}
private function deployAccount($fullchain, $privatekey)
{
$path = '/vhost/?c=ssl&a=ssl';
$post = [
'certificate' => $fullchain,
'certificate_key' => $privatekey,
];
$response = curl_client($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy);
if (strpos($response['body'], '成功')) {
return true;
} elseif (preg_match('/alert\(\'(.*?)\'\)/i', $response['body'], $match)) {
throw new Exception(htmlspecialchars($match[1]));
} elseif (strlen($response['body']) > 3 && strlen($response['body']) < 50) {
throw new Exception(htmlspecialchars($response['body']));
} else {
throw new Exception('原因未知(httpCode=' . $response['code'] . ')');
}
}
private function login()
{
$url = $this->url . $this->path . '/index.php?c=sso&a=hello&url=' . urlencode($this->url . $this->path . '/index.php?');
$response = curl_client($url, null, null, null, null, $this->proxy);
if ($response['code'] == 302 && !empty($response['redirect_url'])) {
$cookie = '';
if (preg_match_all('/Set-Cookie: (.*);/iU', $response['header'], $matchs)) {
foreach ($matchs[1] as $val) {
$arr = explode('=', $val);
if ($arr[1] == '' || $arr[1] == 'deleted') continue;
$cookie .= $val . '; ';
}
$query = parse_url($response['redirect_url'], PHP_URL_QUERY);
parse_str($query, $params);
if (isset($params['r'])) {
$sess_key = $params['r'];
$this->login2($cookie, $sess_key);
$this->cookie = $cookie;
return true;
} else {
throw new Exception('获取SSO凭据失败sess_key获取失败');
}
} else {
throw new Exception('获取SSO凭据失败获取cookie失败');
}
} elseif (strlen($response['body']) > 3 && strlen($response['body']) < 50) {
throw new Exception('获取SSO凭据失败 (' . htmlspecialchars($response['body']) . ')');
} else {
throw new Exception('获取SSO凭据失败 (httpCode=' . $response['code'] . ')');
}
}
private function login2($cookie, $sess_key)
{
$s = md5($sess_key . $this->username . $sess_key . $this->skey);
$url = $this->url . $this->path . '/index.php?c=sso&a=login&name=' . $this->username . '&r=' . $sess_key . '&s=' . $s;
$response = curl_client($url, null, null, $cookie, null, $this->proxy);
if ($response['code'] == 302) {
return true;
} elseif (strlen($response['body']) > 3 && strlen($response['body']) < 50) {
throw new Exception('SSO登录失败 (' . htmlspecialchars($response['body']) . ')');
} else {
throw new Exception('SSO登录失败 (httpCode=' . $response['code'] . ')');
}
}
private function loginVhost($name)
{
$url = $this->url . $this->path . '/index.php?c=vhost&a=impLogin&name=' . $name;
$response = curl_client($url, null, null, $this->cookie, null, $this->proxy);
if ($response['code'] == 302) {
curl_client($this->url . '/vhost/', null, null, $this->cookie, null, $this->proxy);
} else {
throw new Exception('用户面板登录失败 (httpCode=' . $response['code'] . ')');
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

94
app/lib/deploy/kuocai.php Normal file
View File

@@ -0,0 +1,94 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class kuocai implements DeployInterface
{
private $logger;
private $username;
private $password;
private $proxy;
private $token = null;
public function __construct($config)
{
$this->username = $config['username'];
$this->password = $config['password'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->username) || empty($this->password)) {
throw new Exception('请填写控制台账号和密码');
}
$this->request('/login/loginUser', [
'userAccount' => $this->username,
'userPwd' => $this->password,
'remember' => 'true'
]);
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
$id = $config['id'];
if (empty($id)) {
throw new Exception('域名ID不能为空');
}
$this->token = $this->request('/login/loginUser', [
'userAccount' => $this->username,
'userPwd' => $this->password,
'remember' => 'true'
]);
$this->request('/CdnDomainHttps/httpsConfiguration', [
'doMainId' => $id,
'https' => [
'certificate_name' => uniqid('cert_'),
'certificate_source' => '0',
'certificate_value' => $fullchain,
'https_status' => 'on',
'private_key' => $privatekey,
]
], true);
$this->log("域名ID:{$id}更新成功!");
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
private function request($path, $params = null, $json = false)
{
$url = 'https://kuocai.cn' . $path;
$body = $json ? json_encode($params) : $params;
$headers = [];
if ($json) $headers[] = 'Content-Type: application/json';
$response = curl_client(
$url,
$body,
null,
$this->token ? "kuocai_cdn_token={$this->token}" : null,
$headers,
$this->proxy
);
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 'SUCCESS') {
return isset($result['data']) ? $result['data'] : null;
} elseif (isset($result['message'])) {
throw new Exception($result['message']);
} else {
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
}
}

View File

@@ -62,8 +62,8 @@ class lecdn implements DeployInterface
'password' => $this->password,
];
$result = $this->request($path, $params);
if (isset($result['access_token'])) {
$this->accessToken = $result['access_token'];
if (isset($result['token'])) {
$this->accessToken = $result['token'];
} else {
throw new Exception('登录成功获取access_token失败');
}
@@ -83,7 +83,7 @@ class lecdn implements DeployInterface
}
$response = curl_client($url, $body, null, null, $headers, $this->proxy, $method);
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 0) {
if (isset($result['code']) && $result['code'] == 200) {
return isset($result['data']) ? $result['data'] : null;
} elseif (isset($result['message'])) {
throw new Exception($result['message']);

127
app/lib/deploy/mwpanel.php Normal file
View File

@@ -0,0 +1,127 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class mwpanel implements DeployInterface
{
private $logger;
private $url;
private $appid;
private $appsecret;
private $proxy;
public function __construct($config)
{
$this->url = rtrim($config['url'], '/');
$this->appid = $config['appid'];
$this->appsecret = $config['appsecret'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->url) || empty($this->appid) || empty($this->appsecret)) throw new Exception('请填写面板地址和接口密钥');
$path = '/task/count';
$response = $this->request($path);
$result = json_decode($response, true);
if (isset($result['status']) && $result['status'] == true) {
return true;
} else {
throw new Exception(isset($result['msg']) ? $result['msg'] : '面板地址无法连接');
}
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
if ($config['type'] == '1') {
$this->deployPanel($fullchain, $privatekey);
$this->log("面板证书部署成功");
return;
}
$sites = explode("\n", $config['sites']);
$success = 0;
$errmsg = null;
foreach ($sites as $site) {
$siteName = trim($site);
if (empty($siteName)) continue;
try {
$this->deploySite($siteName, $fullchain, $privatekey);
$this->log("网站 {$siteName} 证书部署成功");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("网站 {$siteName} 证书部署失败:" . $errmsg);
}
}
if ($success == 0) {
throw new Exception($errmsg ? $errmsg : '要部署的网站不存在');
}
}
private function deployPanel($fullchain, $privatekey)
{
$path = '/setting/save_panel_ssl';
$data = [
'privateKey' => $privatekey,
'certPem' => $fullchain,
'choose' => 'local',
];
$response = $this->request($path, $data);
$result = json_decode($response, true);
if (isset($result['status']) && $result['status']) {
return true;
} elseif (isset($result['msg'])) {
throw new Exception($result['msg']);
} else {
throw new Exception($response ? $response : '返回数据解析失败');
}
}
private function deploySite($siteName, $fullchain, $privatekey)
{
$path = '/site/set_ssl';
$data = [
'type' => '1',
'siteName' => $siteName,
'key' => $privatekey,
'csr' => $fullchain,
];
$response = $this->request($path, $data);
$result = json_decode($response, true);
if (isset($result['status']) && $result['status']) {
return true;
} elseif (isset($result['msg'])) {
throw new Exception($result['msg']);
} else {
throw new Exception($response ? $response : '返回数据解析失败');
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
private function request($path, $params = null)
{
$url = $this->url . $path;
$headers = [
'app-id: '.$this->appid,
'app-secret: '.$this->appsecret,
];
$response = curl_client($url, $params ? http_build_query($params) : null, null, null, $headers, $this->proxy);
return $response['body'];
}
}

View File

@@ -40,32 +40,35 @@ class opanel implements DeployInterface
$success = 0;
$errmsg = null;
foreach ($data['items'] as $row) {
if (empty($row['primaryDomain'])) continue;
$cert_domains[] = $row['primaryDomain'];
if(!empty($row['domains'])) $cert_domains += explode(',', $row['domains']);
$flag = false;
foreach ($cert_domains as $domain) {
if (in_array($domain, $domains)) {
$flag = true;
break;
if (!empty($data['items'])) {
foreach ($data['items'] as $row) {
if (empty($row['primaryDomain'])) continue;
$cert_domains = [];
$cert_domains[] = $row['primaryDomain'];
if(!empty($row['domains'])) $cert_domains += explode(',', $row['domains']);
$flag = false;
foreach ($cert_domains as $domain) {
if (in_array($domain, $domains)) {
$flag = true;
break;
}
}
}
if ($flag) {
$params = [
'sslID' => $row['id'],
'type' => 'paste',
'certificate' => $fullchain,
'privateKey' => $privatekey,
'description' => '',
];
try {
$this->request('/api/v1/websites/ssl/upload', $params);
$this->log("证书ID:{$row['id']}更新成功!");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("证书ID:{$row['id']}更新失败:" . $errmsg);
if ($flag) {
$params = [
'sslID' => $row['id'],
'type' => 'paste',
'certificate' => $fullchain,
'privateKey' => $privatekey,
'description' => '',
];
try {
$this->request('/api/v1/websites/ssl/upload', $params);
$this->log("证书ID:{$row['id']}更新成功!");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("证书ID:{$row['id']}更新失败:" . $errmsg);
}
}
}
}
@@ -96,7 +99,7 @@ class opanel implements DeployInterface
'1Panel-Token: '.$token,
'1Panel-Timestamp: '.$timestamp
];
$body = $params ? json_encode($params) : null;
$body = $params ? json_encode($params) : '{}';
if($body) $headers[] = 'Content-Type: application/json';
$response = curl_client($url, $body, null, null, $headers, $this->proxy);
$result = json_decode($response['body'], true);

View File

@@ -0,0 +1,98 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class proxmox implements DeployInterface
{
private $logger;
private $url;
private $api_user;
private $api_key;
private $proxy;
public function __construct($config)
{
$this->url = rtrim($config['url'], '/');
$this->api_user = $config['api_user'];
$this->api_key = $config['api_key'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->url) || empty($this->api_user) || empty($this->api_key)) throw new Exception('必填内容不能为空');
$path = '/api2/json/access';
$this->send_request($path);
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
if (empty($config['node'])) throw new Exception('节点名称不能为空');
$cert_hash = openssl_x509_fingerprint($fullchain, 'sha256');
if (!$cert_hash) throw new Exception('证书解析失败');
$path = '/api2/json/nodes/' . $config['node'] . '/certificates/info';
$list = $this->send_request($path);
foreach ($list as $item) {
$fingerprint = strtolower(str_replace(':', '', $item['fingerprint']));
if ($fingerprint == $cert_hash) {
$this->log('节点:' . $config['node'] . ' 证书已存在');
return;
}
}
$path = '/api2/json/nodes/' . $config['node'] . '/certificates/custom';
$params = [
'certificates' => $fullchain,
'key' => $privatekey,
'force' => 1,
'restart' => 1,
];
$this->send_request($path, $params);
$this->log('节点:' . $config['node'] . ' 证书部署成功!');
}
private function send_request($path, $params = null)
{
$url = $this->url . $path;
$headers = ['Authorization: PVEAPIToken=' . $this->api_user . '=' . $this->api_key];
$post = $params ? http_build_query($params) : null;
$response = curl_client($url, $post, null, null, $headers, $this->proxy);
if ($response['code'] == 200) {
$result = json_decode($response['body'], true);
if (isset($result['data'])) {
return $result['data'];
} elseif (isset($result['errors'])) {
if (is_array($result['errors'])) {
$result['errors'] = implode(';', $result['errors']);
}
throw new Exception($result['errors']);
} else {
throw new Exception('返回数据解析失败');
}
} else {
$header = getSubstr($response['header'], ' ', "\r\n");
if ($header) {
throw new Exception($header);
} else {
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

View File

@@ -17,7 +17,7 @@ class qiniu implements DeployInterface
{
$this->AccessKey = $config['AccessKey'];
$this->SecretKey = $config['SecretKey'];
$this->client = new QiniuClient($this->AccessKey, $this->SecretKey);
$this->client = new QiniuClient($this->AccessKey, $this->SecretKey, isset($config['proxy']) ? $config['proxy'] == 1 : false);
}
public function check()
@@ -42,6 +42,8 @@ class qiniu implements DeployInterface
$this->deploy_cdn($domain, $cert_id);
} elseif ($config['product'] == 'oss') {
$this->deploy_oss($domain, $cert_id);
} elseif ($config['product'] == 'pili') {
$this->deploy_pili($config['pili_hub'], $domain, $cert_name);
} else {
throw new Exception('未知的产品类型');
}
@@ -87,6 +89,15 @@ class qiniu implements DeployInterface
$this->log('OSS域名 ' . $domain . ' 证书部署成功!');
}
private function deploy_pili($hub, $domain, $cert_name)
{
$param = [
'CertName' => $cert_name,
];
$this->client->pili_request('POST', '/v2/hubs/'.$hub.'/domains/'.$domain.'/cert', null, $param);
$this->log('视频直播域名 ' . $domain . ' 证书部署成功!');
}
private function get_cert_id($fullchain, $privatekey, $common_name, $cert_name)
{
$cert_id = null;

View File

@@ -68,7 +68,15 @@ class safeline implements DeployInterface
}
}
if ($success == 0) {
throw new Exception($errmsg ? $errmsg : '没有要更新的证书');
$params = [
'manual' => [
'crt' => $fullchain,
'key' => $privatekey,
],
'type' => 2,
];
$this->request('/api/open/cert', $params);
$this->log("证书上传成功!");
}
}

156
app/lib/deploy/synology.php Normal file
View File

@@ -0,0 +1,156 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class synology implements DeployInterface
{
private $logger;
private $url;
private $username;
private $password;
private $version;
private $token;
private $proxy;
public function __construct($config)
{
$this->url = rtrim($config['url'], '/');
$this->username = $config['username'];
$this->password = $config['password'];
$this->version = $config['version'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->url) || empty($this->username) || empty($this->password)) throw new Exception('必填内容不能为空');
$this->login();
}
private function login()
{
$url = $this->url . '/webapi/' . ($this->version == '1' ? 'auth.cgi' : 'entry.cgi');
$params = [
'api' => 'SYNO.API.Auth',
'version' => 6,
'method' => 'login',
'session' => 'webui',
'account' => $this->username,
'passwd' => $this->password,
'format' => 'sid',
'enable_syno_token' => 'yes',
];
$response = curl_client($url, http_build_query($params), null, null, null, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['success']) && $result['success']) {
$this->token = $result['data'];
} elseif (isset($result['error'])) {
throw new Exception('登录失败:' . $result['error']);
} else {
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
$this->login();
$certInfo = openssl_x509_parse($fullchain, true);
$certInfo['validFrom_time_t'];
if (!$certInfo) throw new Exception('证书解析失败');
$url = $this->url . '/webapi/entry.cgi';
$params = [
'api' => 'SYNO.Core.Certificate.CRT',
'version' => 1,
'method' => 'list',
'_sid' => $this->token['sid'],
'SynoToken' => $this->token['synotoken'],
];
$response = curl_client($url . '?' . http_build_query($params), null, null, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['success']) && $result['success']) {
$this->log('获取证书列表成功');
} elseif (isset($result['error'])) {
throw new Exception('获取证书列表失败:' . json_encode($result['error']));
} else {
throw new Exception('获取证书列表失败(httpCode=' . $response['code'] . ')');
}
$id = null;
$validFrom = 0;
foreach ($result['data']['certificates'] as $certificate) {
if ($certificate['subject']['common_name'] == $certInfo['subject']['CN'] || $certificate['desc'] == $config['desc']) {
$id = $certificate['id'];
$validFrom = \DateTime::createFromFormat('M d H:i:s Y T', $certificate['valid_from'])->getTimestamp();
break;
}
}
if ($id) {
if ($validFrom == $certInfo['validFrom_time_t']) {
$this->log('证书ID:' . $id . '已存在,无需更新');
return;
}
$this->import($fullchain, $privatekey, $config, $id);
} else {
$this->import($fullchain, $privatekey, $config);
}
}
private function import($fullchain, $privatekey, $config, $id = null)
{
$url = $this->url . '/webapi/entry.cgi';
$params = [
'api' => 'SYNO.Core.Certificate',
'version' => 1,
'method' => 'import',
'_sid' => $this->token['sid'],
'SynoToken' => $this->token['synotoken'],
];
$privatekey_file = tempnam(sys_get_temp_dir(), 'privatekey');
file_put_contents($privatekey_file, $privatekey);
$fullchain_file = tempnam(sys_get_temp_dir(), 'fullchain');
file_put_contents($fullchain_file, $fullchain);
$post = [
'key' => new \CURLFile($privatekey_file),
'cert' => new \CURLFile($fullchain_file),
'id' => $id,
'desc' => $config['desc'],
];
$response = curl_client($url . '?' . http_build_query($params), $post, null, null, null, $this->proxy, null, 15);
unlink($privatekey_file);
unlink($fullchain_file);
$result = json_decode($response['body'], true);
if ($id) {
if (isset($result['success']) && $result['success']) {
$this->log('证书ID:' . $id . '更新成功!');
} elseif (isset($result['error'])) {
throw new Exception('证书ID:' . $id . '更新失败:' . json_encode($result['error']));
} else {
throw new Exception('证书ID:' . $id . '更新失败(httpCode=' . $response['code'] . ')');
}
} else {
if (isset($result['success']) && $result['success']) {
$this->log('证书上传成功!');
} elseif (isset($result['error'])) {
throw new Exception('证书上传失败:' . json_encode($result['error']));
} else {
throw new Exception('证书上传失败(httpCode=' . $response['code'] . ')');
}
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

View File

@@ -12,12 +12,14 @@ class tencent implements DeployInterface
private $SecretId;
private $SecretKey;
private TencentCloud $client;
private $proxy;
public function __construct($config)
{
$this->SecretId = $config['SecretId'];
$this->SecretKey = $config['SecretKey'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05');
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', null, $this->proxy);
}
public function check()
@@ -35,21 +37,25 @@ class tencent implements DeployInterface
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
if (empty($config['cos_bucket'])) throw new Exception('存储桶名称不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$instance_id = $config['regionid'] . '#' . $config['cos_bucket'] . '#' . $config['domain'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid']);
$instance_id = $config['regionid'] . '|' . $config['cos_bucket'] . '|' . $config['domain'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid'], $this->proxy);
} elseif ($config['product'] == 'tke') {
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
if (empty($config['tke_cluster_id'])) throw new Exception('集群ID不能为空');
if (empty($config['tke_namespace'])) throw new Exception('命名空间不能为空');
if (empty($config['tke_secret'])) throw new Exception('secret名称不能为空');
$instance_id = $config['tke_cluster_id'] . '|' . $config['tke_namespace'] . '|' . $config['tke_secret'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid']);
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid'], $this->proxy);
} elseif ($config['product'] == 'lighthouse') {
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
if (empty($config['lighthouse_id'])) throw new Exception('实例ID不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$instance_id = $config['regionid'] . '|' . $config['lighthouse_id'] . '|' . $config['domain'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid']);
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid'], $this->proxy);
} elseif ($config['product'] == 'ddos') {
if (empty($config['lighthouse_id'])) throw new Exception('实例ID不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$instance_id = $config['lighthouse_id'] . '|' . $config['domain'] . '|443';
} elseif ($config['product'] == 'clb') {
return $this->deploy_clb($cert_id, $config);
} elseif ($config['product'] == 'scf') {
@@ -57,10 +63,10 @@ class tencent implements DeployInterface
} else {
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
if ($config['product'] == 'waf') {
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['region']);
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['region'], $this->proxy);
} elseif (in_array($config['product'], ['tse', 'scf'])) {
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid']);
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid'], $this->proxy);
}
$instance_id = $config['domain'];
}
@@ -104,9 +110,14 @@ class tencent implements DeployInterface
private function deploy_common($product, $cert_id, $instance_id)
{
if (in_array($product, ['cdn', 'waf', 'teo', 'ddos', 'live', 'vod']) && strpos($instance_id, ',') !== false) {
$instance_ids = explode(',', $instance_id);
} else {
$instance_ids = [$instance_id];
}
$param = [
'CertificateId' => $cert_id,
'InstanceIdList' => [$instance_id],
'InstanceIdList' => $instance_ids,
'ResourceType' => $product,
];
$data = $this->client->request('DeployCertificateInstance', $param);
@@ -144,7 +155,7 @@ class tencent implements DeployInterface
if (empty($config['clb_id'])) throw new Exception('负载均衡ID不能为空');
$sni_switch = !empty($config['clb_domain']) ? 1 : 0;
$client = new TencentCloud($this->SecretId, $this->SecretKey, 'clb.tencentcloudapi.com', 'clb', '2018-03-17', $config['regionid']);
$client = new TencentCloud($this->SecretId, $this->SecretKey, 'clb.tencentcloudapi.com', 'clb', '2018-03-17', $config['regionid'], $this->proxy);
$param = [
'LoadBalancerId' => $config['clb_id'],
'Protocol' => 'HTTPS',
@@ -209,7 +220,7 @@ class tencent implements DeployInterface
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new TencentCloud($this->SecretId, $this->SecretKey, 'scf.tencentcloudapi.com', 'scf', '2018-04-16', $config['regionid']);
$client = new TencentCloud($this->SecretId, $this->SecretKey, 'scf.tencentcloudapi.com', 'scf', '2018-04-16', $config['regionid'], $this->proxy);
$param = [
'Domain' => $config['domain'],
];

133
app/lib/deploy/upyun.php Normal file
View File

@@ -0,0 +1,133 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class upyun implements DeployInterface
{
private $logger;
private $username;
private $password;
private $proxy;
private $cookie;
public function __construct($config)
{
$this->username = $config['username'];
$this->password = $config['password'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->username) || empty($this->password)) throw new Exception('用户名或密码不能为空');
$this->login();
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
$this->login();
$url = 'https://console.upyun.com/api/https/certificate/';
$params = [
'certificate' => $fullchain,
'private_key' => $privatekey,
];
$response = curl_client($url, http_build_query($params), null, $this->cookie, null, $this->proxy);
$result = json_decode($response['body'], true);
if ($result['data']['status'] === 0) {
$common_name = $result['data']['result']['commonName'];
$certificate_id = $result['data']['result']['certificate_id'];
$this->log('证书上传成功证书ID:' . $certificate_id);
} elseif (isset($result['data']['message'])) {
throw new Exception('证书上传失败:' . $result['data']['message']);
} else {
throw new Exception('证书上传失败');
}
$url = 'https://console.upyun.com/api/https/certificate/search';
$params = [
'limit' => 100,
'domain' => $common_name,
];
$response = curl_client($url . '?' . http_build_query($params), null, null, $this->cookie, null, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['data']['result']) && is_array($result['data']['result'])) {
$cert_list = $result['data']['result'];
} elseif (isset($result['data']['message'])) {
throw new Exception('查找证书失败:' . $result['data']['message']);
} else {
throw new Exception('查找证书失败');
}
$i = 0;
$d = 0;
foreach ($cert_list as $crt_id => $item) {
if ($crt_id == $certificate_id || $item['commonName'] != $common_name || $item['config_domain'] == 0) {
continue;
}
$url = 'https://console.upyun.com/api/https/migrate/certificate';
$params = [
'new_crt_id' => $certificate_id,
'old_crt_id' => $crt_id,
];
$response = curl_client($url, http_build_query($params), null, $this->cookie, null, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['data']['result']) && $result['data']['result'] == true) {
$i++;
$d += $item['config_domain'];
$this->log('证书ID:' . $crt_id . ' 迁移成功!');
} elseif (isset($result['data']['message'])) {
throw new Exception('证书迁移失败:' . $result['data']['message']);
} else {
throw new Exception('证书迁移失败');
}
}
if ($i == 0) throw new Exception('未找到可迁移的证书');
$this->log('共迁移' . $i . '个证书,关联域名' . $d . '个');
}
private function login()
{
$url = 'https://console.upyun.com/accounts/signin/';
$params = [
'username' => $this->username,
'password' => $this->password,
];
$response = curl_client($url, http_build_query($params), null, null, null, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['data']['result']) && $result['data']['result'] == true) {
$cookie = '';
if (preg_match_all('/Set-Cookie: (.*);/iU', $response['header'], $matchs)) {
foreach ($matchs[1] as $val) {
$arr = explode('=', $val);
if ($arr[1] == '' || $arr[1] == 'deleted') continue;
$cookie .= $val . '; ';
}
} else {
throw new Exception('登录成功获取cookie失败');
}
$this->cookie = $cookie;
return true;
} elseif (isset($result['data']['message'])) {
throw new Exception('登录失败:' . $result['data']['message']);
} else {
throw new Exception('登录失败');
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

131
app/lib/deploy/west.php Normal file
View File

@@ -0,0 +1,131 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class west implements DeployInterface
{
private $logger;
private $username;
private $api_password;
private $baseUrl = 'https://api.west.cn/api/v2';
private $proxy;
public function __construct($config)
{
$this->username = $config['username'];
$this->api_password = $config['api_password'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->username) || empty($this->api_password)) throw new Exception('用户名或API密码不能为空');
$this->execute('/vhost/', ['act' => 'products']);
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
if (empty($config['sitename'])) throw new Exception('FTP账号不能为空');
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'info'
];
try {
$data = $this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('获取虚拟主机SSL配置失败:' . $e->getMessage());
}
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'import',
'keycontent' => $privatekey,
'certcontent' => $fullchain,
];
try {
$this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('上传SSL证书失败:' . $e->getMessage());
}
$this->log('SSL证书上传成功');
if (!isset($data['SSLEnabled']) || $data['SSLEnabled'] == 0) {
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'openssl',
];
try {
$this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('虚拟主机部署SSL失败:' . $e->getMessage());
}
} else {
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'info'
];
try {
$data = $this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('获取虚拟主机SSL配置失败:' . $e->getMessage());
}
if (!empty($data['sslcert']['ssl'])) {
foreach ($data['sslcert']['ssl'] as $domain => $row) {
if (!in_array($domain, $config['domainList'])) continue;
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'clearsslcache',
'sslid' => $row['sysid'],
'dm' => $domain,
];
try {
$this->execute('/vhost/', $params);
$this->log('更新' . $domain . '证书缓存成功');
} catch (Exception $e) {
$this->log('更新' . $domain . '证书缓存失败:' . $e->getMessage());
}
}
}
}
$this->log('虚拟主机' . $config['sitename'] . '部署SSL成功');
}
private function execute($path, $params)
{
$params['username'] = $this->username;
$params['time'] = getMillisecond();
$params['token'] = md5($this->username . $this->api_password . $params['time']);
$response = curl_client($this->baseUrl . $path, str_replace('+', '%20', http_build_query($params)), null, null, null, $this->proxy);
$response = mb_convert_encoding($response['body'], 'UTF-8', 'GBK');
$arr = json_decode($response, true);
if ($arr) {
if ($arr['result'] == 200) {
return isset($arr['data']) ? $arr['data'] : [];
} else {
throw new Exception($arr['msg']);
}
} else {
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

View File

@@ -22,7 +22,8 @@ class aliyun implements DnsInterface
{
$this->AccessKeyId = $config['ak'];
$this->AccessKeySecret = $config['sk'];
$this->client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $this->Endpoint, $this->Version);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $this->Endpoint, $this->Version, $proxy);
$this->domain = $config['domain'];
}
@@ -252,6 +253,46 @@ class aliyun implements DnsInterface
return false;
}
//获取权重配置子域名列表
public function getWeightSubDomains($PageNumber = 1, $PageSize = 20, $SubDomain = null)
{
$param = ['Action' => 'DescribeDNSSLBSubDomains', 'DomainName' => $this->domain, 'PageNumber' => $PageNumber, 'PageSize' => $PageSize];
if (!empty($SubDomain)) {
$param += ['Rr' => $SubDomain];
}
$data = $this->request($param, true);
if ($data) {
$list = $data['SlbSubDomains']['SlbSubDomain'];
$i = 1;
foreach ($list as &$v) {
$v['id'] = $i++;
$v['rr'] = substr($v['SubDomain'], 0, -strlen($this->domain) - 1);
}
return ['total' => $data['TotalCount'], 'list' => $list];
}
return false;
}
//开启关闭权重配置
public function setWeightStatus($SubDomain, $Open, $Type = null, $Line = null)
{
$param = ['Action' => 'SetDNSSLBStatus', 'DomainName' => $this->domain, 'SubDomain' => $SubDomain, 'Open' => $Open == '1' ? 'true' : 'false'];
if (!empty($Type)) {
$param += ['Type' => $Type];
}
if (!empty($Line)) {
$param += ['Line' => $Line];
}
return $this->request($param);
}
//修改权重
public function updateRecordWeight($RecordId, $Weight)
{
$param = ['Action' => 'UpdateDNSSLBWeight', 'RecordId' => $RecordId, 'Weight' => $Weight];
return $this->request($param);
}
private function convertLineCode($line)
{
$convert_dict = ['0' => 'default', '10=1' => 'unicom', '10=0' => 'telecom', '10=3' => 'mobile', '10=2' => 'edu', '3=0' => 'oversea', '10=22' => 'btvn', '80=0' => 'search', '7=0' => 'internal'];
@@ -264,13 +305,13 @@ class aliyun implements DnsInterface
private function request($param, $returnData = false)
{
if (empty($this->AccessKeyId) || empty($this->AccessKeySecret)) return false;
try{
try {
$result = $this->client->request($param);
}catch(Exception $e){
try{
} catch (Exception $e) {
try {
usleep(50000);
$result = $this->client->request($param);
}catch(Exception $e){
} catch (Exception $e) {
$this->setError($e->getMessage());
return false;
}

View File

@@ -20,7 +20,8 @@ class baidu implements DnsInterface
{
$this->AccessKeyId = $config['ak'];
$this->SecretAccessKey = $config['sk'];
$this->client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $proxy);
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
}
@@ -60,8 +61,9 @@ class baidu implements DnsInterface
//获取解析记录列表
public function getDomainRecords($PageNumber = 1, $PageSize = 20, $KeyWord = null, $SubDomain = null, $Value = null, $Type = null, $Line = null, $Status = null)
{
$query = ['rr' => $KeyWord];
$query = [];
if (!isNullOrEmpty($SubDomain)) {
$SubDomain = strtolower($SubDomain);
$query['rr'] = $SubDomain;
}
$data = $this->send_reuqest('GET', '/v1/dns/zone/'.$this->domain.'/record', $query);
@@ -83,6 +85,32 @@ class baidu implements DnsInterface
'UpdateTime' => null,
];
}
if (!isNullOrEmpty($SubDomain)) {
$list = array_values(array_filter($list, function ($v) use ($SubDomain) {
return $v['Name'] == $SubDomain;
}));
} else {
if (!isNullOrEmpty($KeyWord)) {
$list = array_values(array_filter($list, function ($v) use ($KeyWord) {
return strpos($v['Name'], $KeyWord) !== false || strpos($v['Value'], $KeyWord) !== false;
}));
}
if (!isNullOrEmpty($Value)) {
$list = array_values(array_filter($list, function ($v) use ($Value) {
return $v['Value'] == $Value;
}));
}
if (!isNullOrEmpty($Type)) {
$list = array_values(array_filter($list, function ($v) use ($Type) {
return $v['Type'] == $Type;
}));
}
if (!isNullOrEmpty($Status)) {
$list = array_values(array_filter($list, function ($v) use ($Status) {
return $v['Status'] == $Status;
}));
}
}
return ['total' => count($list), 'list' => $list];
}
return false;

View File

@@ -12,6 +12,7 @@ class cloudflare implements DnsInterface
private $error;
private $domain;
private $domainid;
private $proxy;
function __construct($config)
{
@@ -19,6 +20,7 @@ class cloudflare implements DnsInterface
$this->ApiKey = $config['sk'];
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function getError()
@@ -73,17 +75,19 @@ class cloudflare implements DnsInterface
if ($data) {
$list = [];
foreach ($data['result'] as $row) {
$name = $row['zone_name'] == $row['name'] ? '@' : str_replace('.'.$row['zone_name'], '', $row['name']);
$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;
$list[] = [
'RecordId' => $row['id'],
'Domain' => $row['zone_name'],
'Domain' => $this->domain,
'Name' => $name,
'Type' => $row['type'],
'Value' => $row['content'],
'Line' => $row['proxied'] ? '1' : '0',
'TTL' => $row['ttl'],
'MX' => isset($row['priority']) ? $row['priority'] : null,
'Status' => '1',
'Status' => $status,
'Weight' => null,
'Remark' => $row['comment'],
'UpdateTime' => $row['modified_on'],
@@ -105,17 +109,19 @@ class cloudflare implements DnsInterface
{
$data = $this->send_reuqest('GET', '/zones/'.$this->domainid.'/dns_records/'.$RecordId);
if ($data) {
$name = $data['result']['zone_name'] == $data['result']['name'] ? '@' : str_replace('.' . $data['result']['zone_name'], '', $data['result']['name']);
$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;
return [
'RecordId' => $data['result']['id'],
'Domain' => $data['result']['zone_name'],
'Name' => str_replace('.'.$data['result']['zone_name'], '', $data['result']['name']),
'Domain' => $this->domain,
'Name' => $name,
'Type' => $data['result']['type'],
'Value' => $data['result']['content'],
'Line' => $data['result']['proxied'] ? '1' : '0',
'TTL' => $data['result']['ttl'],
'MX' => isset($data['result']['priority']) ? $data['result']['priority'] : null,
'Status' => '1',
'Status' => $status,
'Weight' => null,
'Remark' => $data['result']['comment'],
'UpdateTime' => $data['result']['modified_on'],
@@ -166,7 +172,9 @@ class cloudflare implements DnsInterface
//设置解析记录状态
public function setDomainRecordStatus($RecordId, $Status)
{
return false;
$info = $this->getDomainRecordInfo($RecordId);
$Name = $Status == '1' ? str_replace('_pause', '', $info['Name']) : $info['Name'] . '_pause';
return $this->updateDomainRecord($RecordId, $Name, $info['Type'], $info['Value'], $info['Line'], $info['TTL'], $info['MX'], $info['Weight'], $info['Remark']);
}
//获取解析记录操作日志
@@ -261,6 +269,9 @@ class cloudflare implements DnsInterface
}
$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);

View File

@@ -13,6 +13,7 @@ class dnsla implements DnsInterface
private $error;
private $domain;
private $domainid;
private $proxy;
public function __construct($config)
{
@@ -20,6 +21,7 @@ class dnsla implements DnsInterface
$this->apisecret = $config['sk'];
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function getError()
@@ -249,10 +251,13 @@ class dnsla implements DnsInterface
}
}
private function curl($method, $path, $header, $body = null, $isPut = false)
private function curl($method, $path, $header, $body = null)
{
$url = $this->baseUrl . $path;
$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, $header);

View File

@@ -23,7 +23,8 @@ class dnspod implements DnsInterface
{
$this->SecretId = $config['ak'];
$this->SecretKey = $config['sk'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, $this->endpoint, $this->service, $this->version);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, $this->endpoint, $this->service, $this->version, null, $proxy);
$this->domain = $config['domain'];
}

View File

@@ -20,7 +20,8 @@ class huawei implements DnsInterface
{
$this->AccessKeyId = $config['ak'];
$this->SecretAccessKey = $config['sk'];
$this->client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $proxy);
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
}
@@ -134,7 +135,7 @@ class huawei implements DnsInterface
{
$Name = $this->getHost($Name);
if ($Type == 'TXT' && substr($Value, 0, 1) != '"') $Value = '"' . $Value . '"';
$records = explode(',', $Value);
$records = array_reverse(explode(',', $Value));
$params = ['name' => $Name, 'type' => $this->convertType($Type), 'records' => $records, 'line' => $Line, 'ttl' => intval($TTL), 'description' => $Remark];
if ($Type == 'MX') $params['records'][0] = intval($MX) . ' ' . $Value;
if ($Weight > 0) $params['weight'] = intval($Weight);
@@ -147,7 +148,7 @@ class huawei implements DnsInterface
{
$Name = $this->getHost($Name);
if ($Type == 'TXT' && substr($Value, 0, 1) != '"') $Value = '"' . $Value . '"';
$records = explode(',', $Value);
$records = array_reverse(explode(',', $Value));
$params = ['name' => $Name, 'type' => $this->convertType($Type), 'records' => $records, 'line' => $Line, 'ttl' => intval($TTL), 'description' => $Remark];
if ($Type == 'MX') $params['records'][0] = intval($MX) . ' ' . $Value;
if ($Weight > 0) $params['weight'] = intval($Weight);

View File

@@ -32,7 +32,8 @@ class huoshan implements DnsInterface
{
$this->AccessKeyId = $config['ak'];
$this->SecretAccessKey = $config['sk'];
$this->client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $this->service, $this->version, $this->region);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $this->service, $this->version, $this->region, $proxy);
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
}
@@ -76,7 +77,7 @@ class huoshan implements DnsInterface
{
$query = ['ZID' => intval($this->domainid), 'PageNumber' => $PageNumber, 'PageSize' => $PageSize, 'SearchOrder' => 'desc'];
if (!empty($SubDomain) || !empty($Type) || !empty($Line) || !empty($Value)) {
$query += ['Host' => $SubDomain, 'Value' => $Value, 'Type' => $Type, 'Line' => $Line];
$query += ['Host' => $SubDomain, 'Value' => $Value, 'Type' => $Type, 'Line' => $Line, 'SearchMode' => 'exact'];
} elseif (!empty($KeyWord)) {
$query += ['Host' => $KeyWord];
}

253
app/lib/dns/jdcloud.php Normal file
View File

@@ -0,0 +1,253 @@
<?php
namespace app\lib\dns;
use app\lib\DnsInterface;
use app\lib\client\Jdcloud as JdcloudClient;
use Exception;
class jdcloud implements DnsInterface
{
private $AccessKeyId;
private $AccessKeySecret;
private $endpoint = "domainservice.jdcloud-api.com";
private $service = "domainservice";
private $version = "v2";
private $region = "cn-north-1";
private $error;
private $domain;
private $domainid;
private $domainInfo;
private JdcloudClient $client;
public function __construct($config)
{
$this->AccessKeyId = $config['ak'];
$this->AccessKeySecret = $config['sk'];
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new JdcloudClient($this->AccessKeyId, $this->AccessKeySecret, $this->endpoint, $this->service, $this->region, $proxy);
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
}
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 = 20)
{
$query = ['pageNumber' => $PageNumber, 'pageSize' => $PageSize, 'domainName' => $KeyWord];
$data = $this->send_request('GET', '/domain', $query);
if ($data) {
$list = [];
if (!empty($data['dataList'])) {
foreach ($data['dataList'] as $row) {
$list[] = [
'DomainId' => $row['id'],
'Domain' => $row['domainName'],
'RecordCount' => 0,
];
}
}
return ['total' => $data['totalCount'], 'list' => $list];
}
return false;
}
//获取解析记录列表
public function getDomainRecords($PageNumber = 1, $PageSize = 20, $KeyWord = null, $SubDomain = null, $Value = null, $Type = null, $Line = null, $Status = null)
{
if ($PageSize > 99) $PageSize = 99;
$query = ['pageNumber' => $PageNumber, 'pageSize' => $PageSize];
if (!isNullOrEmpty($SubDomain)) {
$SubDomain = strtolower($SubDomain);
$query += ['search' => $SubDomain];
} elseif (!isNullOrEmpty($KeyWord)) {
$query += ['search' => $KeyWord];
}
$data = $this->send_request('GET', '/domain/'.$this->domainid.'/ResourceRecord', $query);
if ($data) {
$list = [];
foreach ($data['dataList'] as $row) {
if ($row['type'] == 'SRV') {
$row['hostValue'] = $row['mxPriority'].' '.$row['weight'].' '.$row['port'].' '.$row['hostValue'];
}
$list[] = [
'RecordId' => $row['id'],
'Domain' => $this->domain,
'Name' => $row['hostRecord'],
'Type' => $row['type'],
'Value' => $row['hostValue'],
'Line' => array_pop($row['viewValue']),
'TTL' => $row['ttl'],
'MX' => isset($row['mxPriority']) ? $row['mxPriority'] : null,
'Status' => $row['resolvingStatus'] == '2' ? '1' : '0',
'Weight' => $row['weight'],
'Remark' => null,
'UpdateTime' => date('Y-m-d H:i:s', $row['updateTime']),
];
}
if (!isNullOrEmpty($SubDomain) && !empty($list)) {
$list = array_values(array_filter($list, function ($v) use ($SubDomain) {
return $v['Name'] == $SubDomain;
}));
}
return ['total' => $data['totalCount'], 'list' => $list];
}
return false;
}
//获取子域名解析记录列表
public function getSubDomainRecords($SubDomain, $PageNumber = 1, $PageSize = 20, $Type = null, $Line = null)
{
return $this->getDomainRecords($PageNumber, $PageSize, null, $SubDomain, null, $Type, $Line);
}
//获取解析记录详细信息
public function getDomainRecordInfo($RecordId)
{
return false;
}
//添加解析记录
public function addDomainRecord($Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
{
$params = ['hostRecord' => $Name, 'type' => $this->convertType($Type), 'hostValue' => $Value, 'viewValue' => intval($Line), 'ttl' => intval($TTL)];
if ($Type == 'MX') $params['mxPriority'] = intval($MX);
if (!isNullOrEmpty($Weight)) $params['weight'] = intval($Weight);
if ($Type == 'SRV') {
$values = explode(' ', $Value);
$params['mxPriority'] = intval($values[0]);
$params['weight'] = intval($values[1]);
$params['port'] = intval($values[2]);
$params['hostValue'] = $values[3];
}
$data = $this->send_request('POST', '/domain/'.$this->domainid.'/ResourceRecord', ['req'=>$params]);
return is_array($data) ? $data['dataList']['id'] : false;
}
//修改解析记录
public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
{
$params = ['domainName'=>$this->domain, 'hostRecord' => $Name, 'type' => $this->convertType($Type), 'hostValue' => $Value, 'viewValue' => intval($Line), 'ttl' => intval($TTL)];
if ($Type == 'MX') $params['mxPriority'] = intval($MX);
if (!isNullOrEmpty($Weight)) $params['weight'] = intval($Weight);
if ($Type == 'SRV') {
$values = explode(' ', $Value);
$params['mxPriority'] = intval($values[0]);
$params['weight'] = intval($values[1]);
$params['port'] = intval($values[2]);
$params['hostValue'] = $values[3];
}
return $this->send_request('PUT', '/domain/'.$this->domainid.'/ResourceRecord/'.$RecordId, ['req'=>$params]);
}
//修改解析记录备注
public function updateDomainRecordRemark($RecordId, $Remark)
{
return false;
}
//删除解析记录
public function deleteDomainRecord($RecordId)
{
return $this->send_request('DELETE', '/domain/'.$this->domainid.'/ResourceRecord/'.$RecordId);
}
//设置解析记录状态
public function setDomainRecordStatus($RecordId, $Status)
{
$params = ['action' => $Status == '1' ? 'enable' : 'disable'];
$data = $this->send_request('PUT', '/domain/'.$this->domainid.'/ResourceRecord/'.$RecordId.'/status', $params);
return is_array($data);
}
//获取解析记录操作日志
public function getDomainRecordLog($PageNumber = 1, $PageSize = 20, $KeyWord = null, $StartDate = null, $endDate = null)
{
return false;
}
//获取解析线路列表
public function getRecordLine()
{
$domainInfo = $this->getDomainInfo();
if (!$domainInfo) return false;
$packId = $domainInfo['packId'];
$data = $this->send_request('GET', '/domain/'.$this->domainid.'/viewTree', ['packId'=>$packId, 'viewId'=>'0']);
if ($data) {
$list = [];
$this->processLineList($list, $data['data'], null);
return $list;
}
return false;
}
private function processLineList(&$list, $line_list, $parent)
{
foreach ($line_list as $row) {
if ($row['disabled']) continue;
if (!isset($list[$row['value']])) {
$list[$row['value']] = ['name' => $row['label'], 'parent' => $parent];
if (!$row['leaf'] && $row['children']) {
$this->processLineList($list, $row['children'], $row['value']);
}
}
}
}
//获取域名概览信息
public function getDomainInfo()
{
if (!empty($this->domainInfo)) return $this->domainInfo;
$query = ['domainId' => intval($this->domainid)];
$data = $this->send_request('GET', '/domain', $query);
if ($data && $data['dataList']) {
return $data['dataList'][0];
}
return false;
}
//获取域名最低TTL
public function getMinTTL()
{
return false;
}
private function convertType($type)
{
$convert_dict = ['REDIRECT_URL' => 'EXPLICIT_URL', 'FORWARD_URL' => 'IMPLICIT_URL'];
if (array_key_exists($type, $convert_dict)) {
return $convert_dict[$type];
}
return $type;
}
private function send_request($method, $action, $params = [])
{
$path = '/'.$this->version.'/regions/'.$this->region.$action;
try{
return $this->client->request($method, $path, $params);
}catch(Exception $e){
$this->setError($e->getMessage());
return false;
}
}
private function setError($message)
{
$this->error = $message;
//file_put_contents('logs.txt',date('H:i:s').' '.$message."\r\n", FILE_APPEND);
}
}

View File

@@ -12,11 +12,13 @@ class namesilo implements DnsInterface
private $version = '1';
private $error;
private $domain;
private $proxy;
function __construct($config)
{
$this->apikey = $config['sk'];
$this->domain = $config['domain'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function getError()
@@ -77,22 +79,22 @@ class namesilo implements DnsInterface
'UpdateTime' => null,
];
}
if(!empty($SubDomain)){
if(!isNullOrEmpty($SubDomain)){
$list = array_values(array_filter($list, function($v) use ($SubDomain){
return $v['Name'] == $SubDomain;
return strcasecmp($v['Name'], $SubDomain) === 0;
}));
}else{
if(!empty($KeyWord)){
if(!isNullOrEmpty($KeyWord)){
$list = array_values(array_filter($list, function($v) use ($KeyWord){
return strpos($v['Name'], $KeyWord) !== false || strpos($v['Value'], $KeyWord) !== false;
}));
}
if(!empty($Value)){
if(!isNullOrEmpty($Value)){
$list = array_values(array_filter($list, function($v) use ($Value){
return $v['Value'] == $Value;
}));
}
if(!empty($Type)){
if(!isNullOrEmpty($Type)){
$list = array_values(array_filter($list, function($v) use ($Type){
return $v['Type'] == $Type;
}));
@@ -116,8 +118,9 @@ class namesilo implements DnsInterface
}
//添加解析记录
public function addDomainRecord($Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
public function addDomainRecord($Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
{
if ($Name == '@') $Name = '';
$param = ['domain' => $this->domain, 'rrtype' => $Type, 'rrhost' => $Name, 'rrvalue' => $Value, 'rrttl' => $TTL];
if ($Type == 'MX') $param['rrdistance'] = intval($MX);
$data = $this->send_reuqest('dnsAddRecord', $param);
@@ -125,8 +128,9 @@ class namesilo implements DnsInterface
}
//修改解析记录
public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
{
if ($Name == '@') $Name = '';
$param = ['domain' => $this->domain, 'rrid' => $RecordId, 'rrtype' => $Type, 'rrhost' => $Name, 'rrvalue' => $Value, 'rrttl' => $TTL];
if ($Type == 'MX') $param['rrdistance'] = intval($MX);
$data = $this->send_reuqest('dnsUpdateRecord', $param);
@@ -193,7 +197,7 @@ class namesilo implements DnsInterface
$url .= '?' . http_build_query($params);
try{
$response = curl_client($url);
$response = curl_client($url, null, null, null, null, $this->proxy);
}catch(Exception $e){
$this->setError($e->getMessage());
return false;

409
app/lib/dns/powerdns.php Normal file
View File

@@ -0,0 +1,409 @@
<?php
namespace app\lib\dns;
use app\lib\DnsInterface;
use Exception;
class powerdns implements DnsInterface
{
private $url;
private $apikey;
private $server_id = 'localhost';
private $error;
private $domain;
private $domainid;
private $proxy;
function __construct($config)
{
$this->url = 'http://' . $config['ak'] . ':' . $config['sk'] . '/api/v1';
$this->apikey = $config['ext'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
}
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 = 20)
{
$data = $this->send_reuqest('GET', '/servers/' . $this->server_id . '/zones');
if ($data) {
$list = [];
foreach ($data as $row) {
$list[] = [
'DomainId' => $row['id'],
'Domain' => rtrim($row['name'], '.'),
'RecordCount' => 0,
];
}
return ['total' => count($list), 'list' => $list];
}
return false;
}
//获取解析记录列表
public function getDomainRecords($PageNumber = 1, $PageSize = 20, $KeyWord = null, $SubDomain = null, $Value = null, $Type = null, $Line = null, $Status = null)
{
$data = $this->send_reuqest('GET', '/servers/' . $this->server_id . '/zones/' . $this->domainid);
if ($data) {
$list = [];
$rrset_id = 0;
foreach ($data['rrsets'] as &$row) {
$rrset_id++;
$name = $row['name'] == $this->domainid ? '@' : str_replace('.' . $this->domainid, '', $row['name']);
$row['host'] = $name;
$row['id'] = $rrset_id;
$record_id = 0;
foreach ($row['records'] as &$record) {
$record_id++;
$record['id'] = $record_id;
$remark = !empty($row['comments']) ? $row['comments'][0]['content'] : null;
$value = $record['content'];
if ($row['type'] == 'MX') list($record['mx'], $value) = explode(' ', $record['content']);
$list[] = [
'RecordId' => $rrset_id . '_' . $record_id,
'Domain' => $this->domain,
'Name' => $name,
'Type' => $row['type'],
'Value' => $value,
'Line' => 'default',
'TTL' => $row['ttl'],
'MX' => isset($record['mx']) ? $record['mx'] : null,
'Status' => $record['disabled'] ? '0' : '1',
'Weight' => null,
'Remark' => $remark,
'UpdateTime' => null,
];
}
}
cache('powerdns_' . $this->domainid, $data['rrsets'], 86400);
if (!isNullOrEmpty($SubDomain)) {
$list = array_values(array_filter($list, function ($v) use ($SubDomain) {
return strcasecmp($v['Name'], $SubDomain) === 0;
}));
} else {
if (!isNullOrEmpty($KeyWord)) {
$list = array_values(array_filter($list, function ($v) use ($KeyWord) {
return strpos($v['Name'], $KeyWord) !== false || strpos($v['Value'], $KeyWord) !== false;
}));
}
if (!isNullOrEmpty($Value)) {
$list = array_values(array_filter($list, function ($v) use ($Value) {
return $v['Value'] == $Value;
}));
}
if (!isNullOrEmpty($Type)) {
$list = array_values(array_filter($list, function ($v) use ($Type) {
return $v['Type'] == $Type;
}));
}
if (!isNullOrEmpty($Status)) {
$list = array_values(array_filter($list, function ($v) use ($Status) {
return $v['Status'] == $Status;
}));
}
}
return ['total' => count($list), 'list' => $list];
}
return false;
}
//获取子域名解析记录列表
public function getSubDomainRecords($SubDomain, $PageNumber = 1, $PageSize = 20, $Type = null, $Line = null)
{
return $this->getDomainRecords($PageNumber, $PageSize, null, $SubDomain, null, $Type, $Line);
}
//获取解析记录详细信息
public function getDomainRecordInfo($RecordId)
{
return false;
}
//添加解析记录
public function addDomainRecord($Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
{
if ($Type == 'TXT' && substr($Value, 0, 1) != '"') $Value = '"' . $Value . '"';
if (($Type == 'CNAME' || $Type == 'MX') && substr($Value, -1) != '.') $Value .= '.';
if ($Type == 'MX') $Value = intval($MX) . ' ' . $Value;
$records = [];
$rrsets = cache('powerdns_' . $this->domainid);
if ($rrsets) {
$rrsets_filter = array_filter($rrsets, function ($row) use ($Name, $Type) {
return $row['host'] == $Name && $row['type'] == $Type;
});
if (!empty($rrsets_filter)) {
$rrset = $rrsets_filter[array_key_first($rrsets_filter)];
$records = $rrset['records'];
$records_filter = array_filter($records, function ($record) use ($Value) {
return $record['content'] == $Value;
});
if (!empty($records_filter)) {
$this->setError('已存在相同记录');
return false;
}
}
}
$records[] = ['content' => $Value, 'disabled' => false];
return $this->rrset_replace($Name, $Type, $TTL, $records, $Remark);
}
//修改解析记录
public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
{
if ($Type == 'TXT' && substr($Value, 0, 1) != '"') $Value = '"' . $Value . '"';
if (($Type == 'CNAME' || $Type == 'MX') && substr($Value, -1) != '.') $Value .= '.';
if ($Type == 'MX') $Value = intval($MX) . ' ' . $Value;
$rrsets = cache('powerdns_' . $this->domainid);
$add = false;
$res = false;
if ($rrsets) {
[$rrset_id, $record_id] = explode('_', $RecordId);
$exist = false;
foreach ($rrsets as &$rrset) {
if ($rrset['id'] == $rrset_id) {
$records = $rrset['records'];
$records_filter = array_filter($records, function ($record) use ($Value, $record_id) {
return $record['content'] == $Value && $record['id'] != $record_id;
});
if (!empty($records_filter)) {
$this->setError('已存在相同记录');
return false;
}
foreach ($records as $i => &$record) {
if ($record['id'] == $record_id) {
$exist = true;
if ($rrset['host'] == $Name && $rrset['type'] == $Type) {
$record['content'] = $Value;
} else {
unset($records[$i]);
$add = true;
}
break;
}
}
if (!$exist) break;
$records = array_values($records);
if (!empty($records)) {
$res = $this->rrset_replace($rrset['host'], $rrset['type'], $TTL, $records, $Remark);
} else {
$res = $this->rrset_delete($rrset['host'], $rrset['type']);
}
$rrset['records'] = $records;
break;
}
}
if (!$exist) {
$this->setError('记录不存在,请刷新页面重试');
return false;
}
cache('powerdns_' . $this->domainid, $rrsets, 86400);
if ($res && $add) {
$res = $this->addDomainRecord($Name, $Type, $Value, $Line, $TTL, $MX, $Weight, $Remark);
}
return $res;
} else {
$records[] = ['content' => $Value, 'disabled' => false];
return $this->addDomainRecord($Name, $Type, $Value, $Line, $TTL, $MX, $Weight, $Remark);
}
}
//修改解析记录备注
public function updateDomainRecordRemark($RecordId, $Remark)
{
return false;
}
//删除解析记录
public function deleteDomainRecord($RecordId)
{
$rrsets = cache('powerdns_' . $this->domainid);
if (!$rrsets) {
$this->setError('记录不存在,请刷新页面重试');
return false;
}
[$rrset_id, $record_id] = explode('_', $RecordId);
$exist = false;
$res = false;
foreach ($rrsets as &$rrset) {
if ($rrset['id'] == $rrset_id) {
$records = $rrset['records'];
foreach ($records as $i => &$record) {
if ($record['id'] == $record_id) {
$exist = true;
unset($records[$i]);
break;
}
}
if (!$exist) break;
$records = array_values($records);
if (!empty($records)) {
$res = $this->rrset_replace($rrset['host'], $rrset['type'], $rrset['ttl'], $records);
} else {
$res = $this->rrset_delete($rrset['host'], $rrset['type']);
}
$rrset['records'] = $records;
break;
}
}
if (!$exist) {
$this->setError('记录不存在,请刷新页面重试');
return false;
}
cache('powerdns_' . $this->domainid, $rrsets, 86400);
return $res;
}
//设置解析记录状态
public function setDomainRecordStatus($RecordId, $Status)
{
$rrsets = cache('powerdns_' . $this->domainid);
if (!$rrsets) {
$this->setError('记录不存在,请刷新页面重试');
return false;
}
[$rrset_id, $record_id] = explode('_', $RecordId);
$exist = false;
$res = false;
foreach ($rrsets as &$rrset) {
if ($rrset['id'] == $rrset_id) {
$records = $rrset['records'];
foreach ($records as &$record) {
if ($record['id'] == $record_id) {
$exist = true;
$record['disabled'] = $Status == '0';
break;
}
}
if (!$exist) break;
$res = $this->rrset_replace($rrset['host'], $rrset['type'], $rrset['ttl'], $records);
$rrset['records'] = $records;
break;
}
}
if (!$exist) {
$this->setError('记录不存在,请刷新页面重试');
return false;
}
cache('powerdns_' . $this->domainid, $rrsets, 86400);
return $res;
}
//获取解析记录操作日志
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;
}
//获取域名最低TTL
public function getMinTTL()
{
return false;
}
private function rrset_replace($host, $type, $ttl, $records, $remark = null)
{
$name = $host == '@' ? $this->domainid : $host . '.' . $this->domainid;
$rrset = [
'name' => $name,
'type' => $type,
'ttl' => intval($ttl),
'changetype' => 'REPLACE',
'records' => $records,
'comments' => [],
];
if (!empty($remark)) {
$rrset['comments'] = [
['account' => '', 'content' => $remark]
];
}
$param = [
'rrsets' => [
$rrset
],
];
return $this->send_reuqest('PATCH', '/servers/' . $this->server_id . '/zones/' . $this->domainid, $param);
}
private function rrset_delete($host, $type)
{
$name = $host == '@' ? $this->domainid : $host . '.' . $this->domainid;
$param = [
'rrsets' => [
[
'name' => $name,
'type' => $type,
'changetype' => 'DELETE',
]
],
];
return $this->send_reuqest('PATCH', '/servers/' . $this->server_id . '/zones/' . $this->domainid, $param);
}
private function send_reuqest($method, $path, $params = null)
{
$url = $this->url . $path;
$headers[] = 'X-API-Key: ' . $this->apikey;
$body = null;
if ($method == 'GET' || $method == 'DELETE') {
if ($params) {
$url .= '?' . http_build_query($params);
}
} else {
$body = json_encode($params);
$headers[] = 'Content-Type: application/json';
}
try {
$response = curl_client($url, $body, null, null, $headers, $this->proxy, $method);
} catch (Exception $e) {
$this->setError($e->getMessage());
return false;
}
$arr = json_decode($response['body'], true);
if ($response['code'] < 400) {
return is_array($arr) ? $arr : true;
} elseif (isset($arr['error'])) {
$this->setError($arr['error']);
return false;
} elseif (isset($arr['errors'])) {
$this->setError(implode(',', $arr['errors']));
return false;
} else {
$this->setError($response['body']);
return false;
}
}
private function setError($message)
{
$this->error = $message;
//file_put_contents('logs.txt',date('H:i:s').' '.$message."\r\n", FILE_APPEND);
}
}

View File

@@ -15,12 +15,14 @@ class west implements DnsInterface
private $error;
private $domain;
private $domainid;
private $proxy;
public function __construct($config)
{
$this->username = $config['ak'];
$this->api_password = $config['sk'];
$this->domain = $config['domain'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function getError()
@@ -177,10 +179,15 @@ class west implements DnsInterface
private function execute($path, $params)
{
$params['username'] = $this->username;
$params['time'] = $this->getMillisecond();
$params['time'] = getMillisecond();
$params['token'] = md5($this->username.$this->api_password.$params['time']);
$response = $this->curl($path, $params);
$response = mb_convert_encoding($response, 'UTF-8', 'GBK');
try{
$response = curl_client($this->baseUrl . $path, http_build_query($params), null, null, null, $this->proxy);
}catch(\Exception $e){
$this->setError($e->getMessage());
return false;
}
$response = mb_convert_encoding($response['body'], 'UTF-8', 'GBK');
$arr = json_decode($response, true);
if ($arr) {
if ($arr['result'] == 200) {
@@ -195,34 +202,6 @@ class west implements DnsInterface
}
}
private function curl($path, $params = null)
{
$url = $this->baseUrl . $path;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
if ($params) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
}
$response = curl_exec($ch);
$errno = curl_errno($ch);
if ($errno) {
$this->setError('Curl error: ' . curl_error($ch));
}
curl_close($ch);
if ($errno) return false;
return $response;
}
private function getMillisecond()
{
list($s1, $s2) = explode(' ', microtime());
return (float)sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
}
private function setError($message)
{
$this->error = $message;

View File

@@ -79,7 +79,7 @@ class CertDeployService
//重置任务
public function reset()
{
Db::name('cert_deploy')->where('id', $this->task['id'])->data(['status' => 0, 'retry' => 0, 'retrytime' => null, 'issend' => 0])->update();
Db::name('cert_deploy')->where('id', $this->task['id'])->data(['status' => 0, 'retry' => 0, 'retrytime' => null, 'issend' => 0, 'islock' => 0])->update();
//$file_name = app()->getRuntimePath().'log/'.$this->task['processid'].'.log';
//if (file_exists($file_name)) unlink($file_name);
$this->task['status'] = 0;
@@ -131,8 +131,13 @@ class CertDeployService
private function saveLog($txt)
{
if (empty($this->task['processid'])) return;
if (!is_dir(app()->getRuntimePath() . 'log')) mkdir(app()->getRuntimePath() . 'log');
$file_name = app()->getRuntimePath().'log/'.$this->task['processid'].'.log';
$file_exists = file_exists($file_name);
file_put_contents($file_name, $txt . PHP_EOL, FILE_APPEND);
if (!$file_exists) {
@chmod($file_name, 0777);
}
if(php_sapi_name() == 'cli'){
echo $txt . PHP_EOL;
}

View File

@@ -68,7 +68,12 @@ class CertOrderService
$cname = CertHelper::$cert_config[$this->atype]['cname'];
foreach($this->domainList as $domain){
$mainDomain = getMainDomain($domain);
if (!Db::name('domain')->where('name', $mainDomain)->find()) {
$drow = Db::name('domain')->where('name', $mainDomain)->find();
if (!$drow && preg_match('/^xn--/', $mainDomain)) {
$drow = Db::name('domain')->where('name', idn_to_utf8($mainDomain))->find();
}
if (!$drow) {
if (substr($domain, 0, 2) == '*.') $domain = substr($domain, 2);
$cname_row = Db::name('cert_cname')->where('domain', $domain)->where('status', 1)->find();
if (!$cname || !$cname_row) {
$errmsg = '域名'.$domain.'未在本系统添加';
@@ -116,7 +121,9 @@ class CertOrderService
$this->saveLog(date('Y-m-d H:i:s').' - 开始添加DNS记录');
$this->addDns();
$this->saveLog('添加DNS记录成功请等待生效后进行验证...');
Db::name('cert_order')->where('id', $this->order['id'])->update(['retrytime' => date('Y-m-d H:i:s', time() + 300)]);
if (CertHelper::$cert_config[$this->atype]['cname']) {
Db::name('cert_order')->where('id', $this->order['id'])->update(['retrytime' => date('Y-m-d H:i:s', time() + 180)]);
}
return 1;
}
// step4: 查询DNS
@@ -202,7 +209,7 @@ class CertOrderService
//重置订单
public function reset()
{
Db::name('cert_order')->where('id', $this->order['id'])->data(['status' => 0, 'retry' => 0, 'retry2' => 0, 'retrytime' => null, 'processid' => null, 'updatetime' => date('Y-m-d H:i:s'), 'issend' => 0])->update();
Db::name('cert_order')->where('id', $this->order['id'])->data(['status' => 0, 'retry' => 0, 'retry2' => 0, 'retrytime' => null, 'processid' => null, 'updatetime' => date('Y-m-d H:i:s'), 'issend' => 0, 'islock' => 0])->update();
$file_name = app()->getRuntimePath().'log/'.$this->order['processid'].'.log';
if (file_exists($file_name)) unlink($file_name);
$this->order['status'] = 0;
@@ -417,8 +424,13 @@ class CertOrderService
private function saveLog($txt)
{
if (empty($this->order['processid'])) return;
if (!is_dir(app()->getRuntimePath() . 'log')) mkdir(app()->getRuntimePath() . 'log');
$file_name = app()->getRuntimePath().'log/'.$this->order['processid'].'.log';
$file_exists = file_exists($file_name);
file_put_contents($file_name, $txt . PHP_EOL, FILE_APPEND);
if (!$file_exists) {
@chmod($file_name, 0777);
}
if(php_sapi_name() == 'cli'){
echo $txt . PHP_EOL;
}

View File

@@ -13,13 +13,15 @@ class CertTaskService
{
$this->execute_deploy();
$this->execute_order();
(new ExpireNoticeService())->task();
config_set('certtask_time', date("Y-m-d H:i:s"));
echo 'done'.PHP_EOL;
}
private function execute_order()
{
$days = config_get('cert_renewdays', 7);
$list = Db::name('cert_order')->field('id,status,issend')->whereRaw('status NOT IN (3,4) AND (retrytime IS NULL OR retrytime<NOW()) OR status=3 AND expiretime<:expiretime', ['expiretime' => date('Y-m-d H:i:s', time() + $days * 86400)])->select();
$list = Db::name('cert_order')->field('id,status,issend')->whereRaw('status NOT IN (3,4) AND (retrytime IS NULL OR retrytime<NOW()) OR status=3 AND isauto=1 AND expiretime<:expiretime', ['expiretime' => date('Y-m-d H:i:s', time() + $days * 86400)])->select();
//print_r($list);exit;
$failcount = 0;
foreach ($list as $row) {
@@ -31,7 +33,7 @@ class CertTaskService
$retcode = $service->process();
if ($retcode == 3) {
echo 'ID:'.$row['id'].' 证书已签发成功!'.PHP_EOL;
if($row['issend'] == 0) MsgNotice::cert_send($row['id'], true);
if($row['issend'] == 0) MsgNotice::cert_order_send($row['id'], true);
} elseif ($retcode == 1) {
echo 'ID:'.$row['id'].' 添加DNS记录成功'.PHP_EOL;
}
@@ -41,7 +43,7 @@ class CertTaskService
if ($e->getCode() == 102) {
break;
} elseif ($e->getCode() == 103) {
if($row['issend'] == 0) MsgNotice::cert_send($row['id'], false);
if($row['issend'] == 0) MsgNotice::cert_order_send($row['id'], false);
} else {
$failcount++;
}
@@ -66,7 +68,7 @@ class CertTaskService
}
}
$list = Db::name('cert_deploy')->field('id,status,issend')->whereRaw('status IN (0,-1) AND (retrytime IS NULL OR retrytime<NOW())')->select();
$list = Db::name('cert_deploy')->field('id,status,issend')->whereRaw('active=1 AND status IN (0,-1) AND (retrytime IS NULL OR retrytime<NOW())')->select();
//print_r($list);exit;
$count = 0;
foreach ($list as $row) {
@@ -74,14 +76,14 @@ class CertTaskService
$service = new CertDeployService($row['id']);
$service->process();
echo 'ID:'.$row['id'].' 部署任务执行成功!'.PHP_EOL;
if($row['issend'] == 0) MsgNotice::deploy_send($row['id'], true);
if($row['issend'] == 0) MsgNotice::cert_deploy_send($row['id'], true);
$count++;
} catch (Exception $e) {
echo 'ID:'.$row['id'].' '.$e->getMessage().PHP_EOL;
if ($e->getCode() == 102) {
break;
} elseif ($e->getCode() == 103) {
if($row['issend'] == 0) MsgNotice::deploy_send($row['id'], false);
if($row['issend'] == 0) MsgNotice::cert_deploy_send($row['id'], false);
} else {
$count++;
}

View File

@@ -0,0 +1,102 @@
<?php
namespace app\service;
use Exception;
use think\facade\Db;
use app\utils\MsgNotice;
/**
* 域名到期提醒
*/
class ExpireNoticeService
{
public function updateDomainDate($id, $domain)
{
try {
[$regTime, $expireTime] = getDomainDate($domain);
Db::name('domain')->where('id', $id)->update(['regtime' => $regTime, 'expiretime' => $expireTime, 'checktime' => date('Y-m-d H:i:s'), 'checkstatus' => 1]);
return ['code' => 0, 'regTime' => $regTime, 'expireTime' => $expireTime, 'msg' => 'Success'];
} catch (Exception $e) {
Db::name('domain')->where('id', $id)->update(['checktime' => date('Y-m-d H:i:s'), 'checkstatus' => 2]);
return ['code' => -1, 'msg' => $e->getMessage()];
}
}
public function task()
{
$count = $this->refreshDomainList();
if ($count > 0) return;
$days = config_get('expire_noticedays');
$max_day = 30;
if (!empty($days)) {
$days = explode(',', $days);
$days = array_map('intval', $days);
$max_day = max($days) + 1;
}
$count = $this->refreshExpiringDomainList($max_day);
if ($count > 0) return;
if (!empty($days) && (config_get('expire_notice_mail') == '1' || config_get('expire_notice_wxtpl') == '1' || config_get('expire_notice_tgbot') == '1' || config_get('expire_notice_webhook') == '1') && date('H') >= 9) {
$this->noticeExpiringDomainList($max_day, $days);
}
}
private function refreshDomainList()
{
$domainList = Db::name('domain')->field('id,name')->where('expiretime', null)->where('checkstatus', 0)->select();
$count = 0;
foreach ($domainList as $domain) {
$res = $this->updateDomainDate($domain['id'], $domain['name']);
if ($res['code'] == 0) {
echo '域名: ' . $domain['name'] . ' 注册时间: ' . $res['regTime'] . ' 到期时间: ' . $res['expireTime'] . PHP_EOL;
} else {
echo '域名: ' . $domain['name'] . ' 更新失败,' . $res['msg'] . PHP_EOL;
}
$count++;
if ($count >= 5) break;
sleep(1);
}
return $count;
}
private function refreshExpiringDomainList($max_day)
{
$domainList = Db::name('domain')->field('id,name')->whereRaw('expiretime>=(NOW() - INTERVAL 5 DAY) AND expiretime<=(NOW() + INTERVAL ' . $max_day . ' DAY) AND checktime<=(NOW() - INTERVAL 1 DAY)')->select();
$count = 0;
foreach ($domainList as $domain) {
$res = $this->updateDomainDate($domain['id'], $domain['name']);
if ($res['code'] == 0) {
echo '域名: ' . $domain['name'] . ' 注册时间: ' . $res['regTime'] . ' 到期时间: ' . $res['expireTime'] . PHP_EOL;
} else {
echo '域名: ' . $domain['name'] . ' 更新失败,' . $res['msg'] . PHP_EOL;
}
$count++;
if ($count >= 5) break;
sleep(1);
}
return $count;
}
private function noticeExpiringDomainList($max_day, $days)
{
$domainList = Db::name('domain')->field('id,name,expiretime')->whereRaw('expiretime>=NOW() AND expiretime<=(NOW() + INTERVAL ' . $max_day . ' DAY) AND is_notice=1 AND (noticetime IS NULL OR noticetime<=(NOW() - INTERVAL 20 HOUR))')->order('expiretime', 'asc')->select();
$noticeList = [];
foreach ($domainList as $domain) {
$expireDay = intval((strtotime($domain['expiretime']) - time()) / 86400);
if (in_array($expireDay, $days)) {
$noticeList[$expireDay][] = ['id' => $domain['id'], 'name' => $domain['name'], 'expiretime' => $domain['expiretime']];
}
}
if (!empty($noticeList)) {
foreach ($noticeList as $day => $list) {
$ids = array_column($list, 'id');
Db::name('domain')->whereIn('id', $ids)->update(['noticetime' => date('Y-m-d H:i:s')]);
MsgNotice::expire_notice_send($day, $list);
echo '域名到期提醒: ' . $day . '天内到期的' . count($ids) . '个域名已发送' . PHP_EOL;
}
}
}
}

View File

@@ -146,17 +146,22 @@ class OptimizeService
if (empty($iplist)) {
continue;
}
$record_num = $row['recordnum'];
$get_ips = array_column($iplist, 'ip');
if ($drow['type'] == 'huawei') {
sort($get_ips);
$get_ips = array_slice($get_ips, 0, $row['recordnum']);
$get_ips = [implode(',', $get_ips)];
$row['recordnum'] = 1;
$record_num = 1;
}
if ($row['type'] == 1 && $line == 'CT') {
$line = 'DEF';
}
if (!isset(DnsHelper::$line_name[$drow['type']][$line])) {
continue;
}
$line_name = DnsHelper::$line_name[$drow['type']][$line];
$this->process_dns_line($dns, $row, $domainRecords['list'], $get_ips, $line_name, $ip_type);
$this->process_dns_line($dns, $row, $domainRecords['list'], $record_num, $get_ips, $line_name, $ip_type);
}
}
@@ -164,9 +169,8 @@ class OptimizeService
}
//处理单个线路的解析记录
private function process_dns_line($dns, $row, $record_list, $get_ips, $line_name, $ip_type)
private function process_dns_line($dns, $row, $record_list, $record_num, $get_ips, $line_name, $ip_type)
{
$record_num = $row['recordnum'];
$records = array_filter($record_list, function ($v) use ($line_name) {
return $v['Line'] == $line_name;
});

View File

@@ -83,6 +83,9 @@ class TaskRunner
if ($row['type'] == 2) {
$dns = DnsHelper::getModel2($drow);
$recordinfo = json_decode($row['recordinfo'], true);
if ($drow['type'] == 'cloudflare' && $row['cdn'] == 1) {
$recordinfo['Line'] = '1';
}
$res = $dns->updateDomainRecord($row['recordid'], $row['rr'], getDnsType($row['backup_value']), $row['backup_value'], $recordinfo['Line'], $recordinfo['TTL']);
if (!$res) {
$this->db()->name('log')->insert(['uid' => 0, 'domain' => $drow['name'], 'action' => '修改解析失败', 'data' => $dns->getError(), 'addtime' => date("Y-m-d H:i:s")]);
@@ -98,6 +101,9 @@ class TaskRunner
if ($row['type'] == 2) {
$dns = DnsHelper::getModel2($drow);
$recordinfo = json_decode($row['recordinfo'], true);
if ($drow['type'] == 'cloudflare' && $row['cdn'] == 1) {
$recordinfo['Line'] = '0';
}
$res = $dns->updateDomainRecord($row['recordid'], $row['rr'], getDnsType($row['main_value']), $row['main_value'], $recordinfo['Line'], $recordinfo['TTL']);
if (!$res) {
$this->db()->name('log')->insert(['uid' => 0, 'domain' => $drow['name'], 'action' => '修改解析失败', 'data' => $dns->getError(), 'addtime' => date("Y-m-d H:i:s")]);

View File

@@ -5,7 +5,7 @@ CREATE TABLE `dnsmgr_config` (
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `dnsmgr_config` VALUES ('version', '1021');
INSERT INTO `dnsmgr_config` VALUES ('version', '1033');
INSERT INTO `dnsmgr_config` VALUES ('notice_mail', '0');
INSERT INTO `dnsmgr_config` VALUES ('notice_wxtpl', '0');
INSERT INTO `dnsmgr_config` VALUES ('mail_smtp', 'smtp.qq.com');
@@ -18,6 +18,7 @@ CREATE TABLE `dnsmgr_account` (
`ak` varchar(256) DEFAULT NULL,
`sk` varchar(256) DEFAULT NULL,
`ext` varchar(256) DEFAULT NULL,
`proxy` tinyint(1) NOT NULL DEFAULT '0',
`remark` varchar(100) DEFAULT NULL,
`addtime` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
@@ -34,6 +35,12 @@ CREATE TABLE `dnsmgr_domain` (
`is_sso` tinyint(1) NOT NULL DEFAULT '0',
`recordcount` int(1) NOT NULL DEFAULT '0',
`remark` varchar(100) DEFAULT NULL,
`is_notice` tinyint(1) NOT NULL DEFAULT '0',
`regtime` datetime DEFAULT NULL,
`expiretime` datetime DEFAULT NULL,
`checktime` datetime DEFAULT NULL,
`noticetime` datetime DEFAULT NULL,
`checkstatus` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -95,6 +102,7 @@ CREATE TABLE `dnsmgr_dmtask` (
`timeout` tinyint(5) NOT NULL DEFAULT 2,
`remark` varchar(100) DEFAULT NULL,
`proxy` tinyint(1) NOT NULL DEFAULT 0,
`cdn` tinyint(1) NOT NULL DEFAULT 0,
`addtime` int(11) NOT NULL DEFAULT 0,
`checktime` int(11) NOT NULL DEFAULT 0,
`checknexttime` int(11) NOT NULL DEFAULT 0,

View File

@@ -149,4 +149,18 @@ CREATE TABLE IF NOT EXISTS `dnsmgr_cert_cname` (
`addtime` datetime DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `dnsmgr_account`
ADD COLUMN `proxy` tinyint(1) NOT NULL DEFAULT '0';
ALTER TABLE `dnsmgr_dmtask`
ADD COLUMN `cdn` tinyint(1) NOT NULL DEFAULT 0;
ALTER TABLE `dnsmgr_domain`
ADD COLUMN `is_notice` tinyint(1) NOT NULL DEFAULT '0',
ADD COLUMN `regtime` datetime DEFAULT NULL,
ADD COLUMN `expiretime` datetime DEFAULT NULL,
ADD COLUMN `checktime` datetime DEFAULT NULL,
ADD COLUMN `noticetime` datetime DEFAULT NULL,
ADD COLUMN `checkstatus` tinyint(1) NOT NULL DEFAULT '0';

View File

@@ -13,6 +13,9 @@ class CertDnsUtils
$cnameDomainList = [];
foreach ($dnsList as $mainDomain => $list) {
$drow = Db::name('domain')->alias('A')->join('account B', 'A.aid = B.id')->where('A.name', $mainDomain)->field('A.*,B.type')->find();
if (!$drow && preg_match('/^xn--/', $mainDomain)) {
$drow = Db::name('domain')->alias('A')->join('account B', 'A.aid = B.id')->where('A.name', idn_to_utf8($mainDomain))->field('A.*,B.type')->find();
}
if (!$drow) {
if ($cname) {
foreach ($list as $key => $row) {
@@ -39,6 +42,9 @@ class CertDnsUtils
usort($list, function ($a, $b) {
return strcmp($a['name'], $b['name']);
});
if ($drow['type'] == 'huawei') {
$list = self::getHuaweiDnsRecords($list);
}
$records = [];
foreach ($list as $row) {
$domain = $row['name'] . '.' . $mainDomain;
@@ -46,7 +52,8 @@ class CertDnsUtils
if (!$records[$row['name']]) throw new Exception('获取'.$domain.'记录列表失败,'.$dns->getError());
$filter_records = array_filter($records[$row['name']]['list'], function ($v) use ($row) {
return $v['Type'] == $row['type'] && $v['Value'] == $row['value'];
if (is_array($v['Value'])) $v['Value'] = implode(',', $v['Value']);
return $v['Type'] == $row['type'] && ($v['Value'] == $row['value'] || rtrim($v['Value'], '.') == $row['value']);
});
if (!empty($filter_records)) {
foreach ($filter_records as $recordid => $record) {
@@ -66,7 +73,8 @@ class CertDnsUtils
}
}
$res = $dns->addDomainRecord($row['name'], $row['type'], $row['value'], DnsHelper::$line_name[$drow['type']]['DEF'], 600);
$ttl = $drow['type'] == 'namesilo' ? 3600 : 600;
$res = $dns->addDomainRecord($row['name'], $row['type'], $row['value'], DnsHelper::$line_name[$drow['type']]['DEF'], $ttl);
if (!$res && $row['type'] != 'CAA') throw new Exception('添加'.$domain.'解析记录失败,' . $dns->getError());
$log('Add DNS Record: '.$domain.' '.$row['type'].' '.$row['value']);
}
@@ -76,11 +84,30 @@ class CertDnsUtils
}
}
private static function getHuaweiDnsRecords($list)
{
//将name相同的TXT记录合并
$txt_records = [];
foreach ($list as $key => $row) {
if ($row['type'] == 'TXT') {
$txt_records[$row['name']][] = $row['value'];
unset($list[$key]);
}
}
foreach ($txt_records as $name => $rows) {
$list[] = ['name' => $name, 'type' => 'TXT', 'value' => '"' . implode('","', $rows) . '"'];
}
return $list;
}
public static function delDns($dnsList, callable $log, $cname = false)
{
$cnameDomainList = [];
foreach ($dnsList as $mainDomain => $list) {
$drow = Db::name('domain')->alias('A')->join('account B', 'A.aid = B.id')->where('A.name', $mainDomain)->field('A.*,B.type')->find();
if (!$drow && preg_match('/^xn--/', $mainDomain)) {
$drow = Db::name('domain')->alias('A')->join('account B', 'A.aid = B.id')->where('A.name', idn_to_utf8($mainDomain))->field('A.*,B.type')->find();
}
if (!$drow) {
if ($cname) {
foreach ($list as $key => $row) {
@@ -107,6 +134,9 @@ class CertDnsUtils
usort($list, function ($a, $b) {
return strcmp($a['name'], $b['name']);
});
if ($drow['type'] == 'huawei') {
$list = self::getHuaweiDnsRecords($list);
}
$records = [];
foreach ($list as $row) {
//if ($row['type'] == 'CAA') continue;
@@ -115,6 +145,7 @@ class CertDnsUtils
if (!$records[$row['name']]) throw new Exception('获取'.$domain.'记录列表失败,'.$dns->getError());
$filter_records = array_filter($records[$row['name']]['list'], function ($v) use ($row) {
if (is_array($v['Value'])) $v['Value'] = implode(',', $v['Value']);
return $v['Type'] == $row['type'] && ($v['Value'] == $row['value'] || rtrim($v['Value'], '.') == $row['value']);
});
if (empty($filter_records)) continue;

View File

@@ -74,9 +74,13 @@ class CheckUtils
public static function tcp($target, $port, $timeout)
{
if (substr($target, -1) == '.') $target = substr($target, 0, -1);
if (!filter_var($target, FILTER_VALIDATE_IP) && checkDomain($target)) {
$target = gethostbyname($target);
if (!$target) return ['status' => false, 'error' => 'DNS resolve failed', 'usetime' => 0];
if (!$target) return ['status' => false, 'errmsg' => 'DNS resolve failed', 'usetime' => 0];
}
if (filter_var($target, FILTER_VALIDATE_IP) && strpos($target, ':') !== false) {
$target = '['.$target.']';
}
$starttime = getMillisecond();
$fp = @fsockopen($target, $port, $errCode, $errStr, $timeout);
@@ -93,13 +97,14 @@ class CheckUtils
public static function ping($target)
{
if (!function_exists('exec')) return ['status' => false, 'error' => 'exec函数不可用', 'usetime' => 0];
if (!function_exists('exec')) return ['status' => false, 'errmsg' => 'exec函数不可用', 'usetime' => 0];
if (substr($target, -1) == '.') $target = substr($target, 0, -1);
if (!filter_var($target, FILTER_VALIDATE_IP) && checkDomain($target)) {
$target = gethostbyname($target);
if (!$target) return ['status' => false, 'error' => 'DNS resolve failed', 'usetime' => 0];
if (!$target) return ['status' => false, 'errmsg' => 'DNS resolve failed', 'usetime' => 0];
}
if (!filter_var($target, FILTER_VALIDATE_IP)) {
return ['status' => false, 'error' => 'Invalid IP address', 'usetime' => 0];
return ['status' => false, 'errmsg' => 'Invalid IP address', 'usetime' => 0];
}
$timeout = 1;
exec('ping -c 1 -w '.$timeout.' '.$target.'', $output, $return_var);

View File

@@ -13,7 +13,7 @@ class MsgNotice
{
if ($action == 1) {
$mail_title = 'DNS容灾切换-发生告警通知';
$mail_content = '尊敬的系统管理员,您好:<br/>您的域名 <b>'.$task['domain'].'</b> 的 <b>'.$task['main_value'].'</b> 记录发生了异常';
$mail_content = '尊敬的用户,您好:<br/>您的域名 <b>'.$task['domain'].'</b> 的 <b>'.$task['main_value'].'</b> 记录发生了异常';
if ($task['type'] == 2) {
$mail_content .= ',已自动切换为备用解析记录 '.$task['backup_value'].' ';
} elseif ($task['type'] == 1) {
@@ -22,11 +22,11 @@ class MsgNotice
$mail_content .= ',请及时处理';
}
if (!empty($result['errmsg'])) {
$mail_content .= '。<br/>异常信息:'.$result['errmsg'];
$mail_content .= '。<br/>异常信息:<font color="warning">'.$result['errmsg'].'</font>';
}
} else {
$mail_title = 'DNS容灾切换-恢复正常通知';
$mail_content = '尊敬的系统管理员,您好:<br/>您的域名 <b>'.$task['domain'].'</b> 的 <b>'.$task['main_value'].'</b> 记录已恢复正常';
$mail_content = '尊敬的用户,您好:<br/>您的域名 <b>'.$task['domain'].'</b> 的 <b>'.$task['main_value'].'</b> 记录已恢复正常';
if ($task['type'] == 2) {
$mail_content .= ',已自动切换回当前解析记录';
} elseif ($task['type'] == 1) {
@@ -41,7 +41,7 @@ class MsgNotice
if (!empty($task['remark'])) {
$mail_content .= '<br/>备注:'.$task['remark'];
}
$mail_content .= '<br/>'.self::$sitename.'<br/>'.date('Y-m-d H:i:s');
$mail_content .= '<br/><font color="grey">'.self::$sitename.'</font><br/><font color="grey">'.date('Y-m-d H:i:s').'</font>';
if (config_get('notice_mail') == 1) {
$mail_name = config_get('mail_recv') ? config_get('mail_recv') : config_get('mail_name');
@@ -49,16 +49,20 @@ class MsgNotice
}
if (config_get('notice_wxtpl') == 1) {
$content = str_replace(['<br/>', '<b>', '</b>'], ["\n\n", '**', '**'], $mail_content);
self::send_wechat_tplmsg($mail_title, $content);
self::send_wechat_tplmsg($mail_title, strip_tags($content));
}
if (config_get('notice_tgbot') == 1) {
$content = str_replace('<br/>', "\n", $mail_content);
$content = "<strong>".$mail_title."</strong>\n".$content;
$content = "<strong>".$mail_title."</strong>\n".strip_tags($content);
self::send_telegram_bot($content);
}
if (config_get('notice_webhook') == 1) {
$content = str_replace(['<br/>', '<b>', '</b>'], ["\n", '**', '**'], $mail_content);
self::send_webhook($mail_title, $content);
}
}
public static function cert_send($id, $result)
public static function cert_order_send($id, $result)
{
$row = Db::name('cert_order')->field('id,aid,issuetime,expiretime,issuer,status,error')->where('id', $id)->find();
if (!$row) return;
@@ -71,7 +75,7 @@ class MsgNotice
} else {
$mail_title = $domainList[0] . '域名SSL证书签发成功通知';
}
$mail_content = '尊敬的用户您好您的SSL证书已签发成功<br/>证书账户:'.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')<br/>证书域名:'.implode('、', $domainList).'<br/>签发时间:'.$row['issuetime'].'<br/>到期时间:'.$row['expiretime'].'<br/>颁发机构:'.$row['issuer'];
$mail_content = '尊敬的用户您好您的SSL证书已签发成功<br/><b>证书账户:</b> '.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')<br/><b>证书域名:</b> '.implode('、', $domainList).'<br/><b>签发时间:</b> '.$row['issuetime'].'<br/><b>到期时间:</b> '.$row['expiretime'].'<br/><b>颁发机构:</b> '.$row['issuer'];
} else {
$status_arr = [0 => '失败', -1 => '购买证书失败', -2 => '创建订单失败', -3 => '添加DNS失败', -4 => '验证DNS失败', -5 => '验证订单失败', -6 => '订单验证未通过', -7 => '签发证书失败'];
if(count($domainList) > 1){
@@ -79,27 +83,15 @@ class MsgNotice
}else{
$mail_title = $domainList[0].'域名SSL证书'.$status_arr[$row['status']].'通知';
}
$mail_content = '尊敬的用户您好您的SSL证书'.$status_arr[$row['status']].'<br/>证书账户:'.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')<br/>证书域名:'.implode('、', $domainList).'<br/>失败时间:'.date('Y-m-d H:i:s').'<br/>失败原因:'.$row['error'];
$mail_content = '尊敬的用户您好您的SSL证书'.$status_arr[$row['status']].'<br/><b>证书账户:</b> '.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')<br/><b>证书域名:</b> '.implode('、', $domainList).'<br/><b>失败时间:</b> '.date('Y-m-d H:i:s').'<br/><b>失败原因:</b> <font color="warning">'.$row['error'].'</font>';
}
$mail_content .= '<br/>'.self::$sitename.'<br/>'.date('Y-m-d H:i:s');
$mail_content .= '<br/><font color="grey">'.self::$sitename.'</font><br/><font color="grey">'.date('Y-m-d H:i:s').'</font>';
if (config_get('cert_notice_mail') == 1 || config_get('cert_notice_mail') == 2 && !$result) {
$mail_name = config_get('mail_recv') ? config_get('mail_recv') : config_get('mail_name');
self::send_mail($mail_name, $mail_title, $mail_content);
}
if (config_get('cert_notice_wxtpl') == 1 || config_get('cert_notice_wxtpl') == 2 && !$result) {
$content = str_replace(['<br/>', '<b>', '</b>'], ["\n\n", '**', '**'], $mail_content);
self::send_wechat_tplmsg($mail_title, $content);
}
if (config_get('cert_notice_tgbot') == 1 || config_get('cert_notice_tgbot') == 2 && !$result) {
$content = str_replace('<br/>', "\n", $mail_content);
$content = "<strong>" . $mail_title . "</strong>\n" . $content;
self::send_telegram_bot($content);
}
self::cert_send($mail_title, $mail_content, $result);
Db::name('cert_order')->where('id', $id)->update(['issend' => 1]);
}
public static function deploy_send($id, $result)
public static function cert_deploy_send($id, $result)
{
$row = Db::name('cert_deploy')->field('id,aid,oid,remark,status,error')->where('id', $id)->find();
if (!$row) return;
@@ -108,28 +100,65 @@ class MsgNotice
$typename = DeployHelper::$deploy_config[$account['type']]['name'];
$mail_title = $typename;
if(!empty($row['remark'])) $mail_title .= '('.$row['remark'].')';
$mail_title .= '证书部署'.($result?'成功':'失败').'通知';
$mail_title .= 'SSL证书部署'.($result?'成功':'失败').'通知';
if ($result) {
$mail_content = '尊敬的用户您好您的SSL证书已成功部署到'.$typename.'<br/>自动部署账户:['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')<br/>关联SSL证书['.$row['oid'].']'.implode('、', $domainList).'<br/>任务备注:'.($row['remark']?$row['remark']:'无');
$mail_content = '尊敬的用户您好您的SSL证书已成功部署到'.$typename.'<br/><b>自动部署账户:</b> ['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')<br/><b>关联SSL证书</b> ['.$row['oid'].']'.implode('、', $domainList).'<br/><b>任务备注:</b> '.($row['remark']?$row['remark']:'无');
} else {
$mail_content = '尊敬的用户您好您的SSL证书部署失败<br/>失败原因:'.$row['error'].'<br/>自动部署账户:['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')<br/>关联SSL证书['.$row['oid'].']'.implode('、', $domainList).'<br/>任务备注:'.($row['remark']?$row['remark']:'无');
$mail_content = '尊敬的用户您好您的SSL证书部署失败<br/><b>失败原因:</b> <font color="warning">'.$row['error'].'</font><br/><b>自动部署账户:</b> ['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')<br/><b>关联SSL证书</b> ['.$row['oid'].']'.implode('、', $domainList).'<br/><b>任务备注:</b> '.($row['remark']?$row['remark']:'无');
}
$mail_content .= '<br/>'.self::$sitename.'<br/>'.date('Y-m-d H:i:s');
$mail_content .= '<br/><font color="grey">'.self::$sitename.'</font><br/><font color="grey">'.date('Y-m-d H:i:s').'</font>';
self::cert_send($mail_title, $mail_content, $result);
Db::name('cert_deploy')->where('id', $id)->update(['issend' => 1]);
}
private static function cert_send($mail_title, $mail_content, $result)
{
if (config_get('cert_notice_mail') == 1 || config_get('cert_notice_mail') == 2 && !$result) {
$mail_name = config_get('mail_recv') ? config_get('mail_recv') : config_get('mail_name');
self::send_mail($mail_name, $mail_title, $mail_content);
}
if (config_get('cert_notice_wxtpl') == 1 || config_get('cert_notice_wxtpl') == 2 && !$result) {
$content = str_replace(['<br/>', '<b>', '</b>'], ["\n\n", '**', '**'], $mail_content);
self::send_wechat_tplmsg($mail_title, $content);
self::send_wechat_tplmsg($mail_title, strip_tags($content));
}
if (config_get('cert_notice_tgbot') == 1 || config_get('cert_notice_tgbot') == 2 && !$result) {
$content = str_replace('<br/>', "\n", $mail_content);
$content = "<strong>" . $mail_title . "</strong>\n" . $content;
$content = "<strong>".$mail_title."</strong>\n".strip_tags($content);
self::send_telegram_bot($content);
}
Db::name('cert_deploy')->where('id', $id)->update(['issend' => 1]);
if (config_get('cert_notice_webhook') == 1) {
$content = str_replace(['*', '<br/>', '<b>', '</b>'], ['\*', "\n", '**', '**'], $mail_content);
self::send_webhook($mail_title, $content);
}
}
public static function expire_notice_send($day, $list)
{
$mail_title = '您有'.count($list).'个域名即将在'.$day.'天后到期';
$mail_content = '尊敬的用户,您好:您有'.count($list).'个域名即将在'.$day.'天后到期!<br/><b>域名&到期时间:</b><br/>';
foreach ($list as $domain) {
$mail_content .= '<b>'.$domain['name'].'</b> - '.$domain['expiretime'].'<br/>';
}
$mail_content .= '<br/><font color="grey">'.self::$sitename.'</font><br/><font color="grey">'.date('Y-m-d H:i:s').'</font>';
if (config_get('expire_notice_mail') == 1 || config_get('expire_notice_mail') == 2) {
$mail_name = config_get('mail_recv') ? config_get('mail_recv') : config_get('mail_name');
self::send_mail($mail_name, $mail_title, $mail_content);
}
if (config_get('expire_notice_wxtpl') == 1 || config_get('expire_notice_wxtpl') == 2) {
$content = str_replace(['<br/>', '<b>', '</b>'], ["\n\n", '**', '**'], $mail_content);
self::send_wechat_tplmsg($mail_title, strip_tags($content));
}
if (config_get('expire_notice_tgbot') == 1 || config_get('expire_notice_tgbot') == 2) {
$content = str_replace('<br/>', "\n", $mail_content);
$content = "<strong>".$mail_title."</strong>\n".strip_tags($content);
self::send_telegram_bot($content);
}
if (config_get('expire_notice_webhook') == 1) {
$content = str_replace(['*', '<br/>', '<b>', '</b>'], ['\*', "\n", '**', '**'], $mail_content);
self::send_webhook($mail_title, $content);
}
}
public static function send_mail($to, $sub, $msg)
@@ -187,7 +216,7 @@ class MsgNotice
if (isset($arr['success']) && $arr['success'] == true) {
return true;
} else {
return $arr['msg'];
return isset($arr['msg']) ? $arr['msg'] : '请求失败';
}
}
@@ -196,14 +225,62 @@ class MsgNotice
$tgbot_token = config_get('tgbot_token');
$tgbot_chatid = config_get('tgbot_chatid');
if (!$tgbot_token || !$tgbot_chatid) return false;
$url = 'https://api.telegram.org/bot'.$tgbot_token.'/sendMessage';
$tgbot_url = 'https://api.telegram.org';
if (config_get('tgbot_proxy') == 2) {
$tgbot_url_n = config_get('tgbot_url');
if (!empty($tgbot_url_n)) {
$tgbot_url = rtrim($tgbot_url_n, '/');
}
}
$url = $tgbot_url.'/bot'.$tgbot_token.'/sendMessage';
$post = ['chat_id' => $tgbot_chatid, 'text' => $content, 'parse_mode' => 'HTML'];
$result = self::telegram_curl($url, http_build_query($post));
$arr = json_decode($result, true);
if (isset($arr['ok']) && $arr['ok'] == true) {
return true;
} else {
return $arr['description'];
return isset($arr['description']) ? $arr['description'] : '请求失败';
}
}
public static function send_webhook($title, $content)
{
$url = config_get('webhook_url');
if (!$url || !parse_url($url)) return false;
if (strpos($url, 'oapi.dingtalk.com')) {
$content = '### '.$title." \n ".str_replace("\n", " \n ", $content);
$post = [
'msgtype' => 'markdown',
'markdown' => [
'title' => $title,
'text' => $content,
],
];
} elseif (strpos($url, 'qyapi.weixin.qq.com')) {
$content = '## '.$title."\n".$content;
$post = [
'msgtype' => 'markdown',
'markdown' => [
'content' => $content,
],
];
} elseif (strpos($url, 'open.feishu.cn') || strpos($url, 'open.larksuite.com')) {
$content = str_replace(['\*', '**'], ['*', ''], strip_tags($content));
$post = [
'msg_type' => 'text',
'content' => [
'text' => $content,
],
];
} else {
return '不支持的Webhook地址';
}
$result = get_curl($url, json_encode($post), 0, 0, 0, 0, 0, ['Content-Type: application/json; charset=UTF-8']);
$arr = json_decode($result, true);
if (isset($arr['errcode']) && $arr['errcode'] == 0 || isset($arr['code']) && $arr['code'] == 0) {
return true;
} else {
return isset($arr['errmsg']) ? $arr['errmsg'] : (isset($arr['msg']) ? $arr['msg'] : '请求失败');
}
}
@@ -211,28 +288,10 @@ class MsgNotice
{
$ch = curl_init();
if (config_get('tgbot_proxy') == 1) {
$proxy_server = config_get('proxy_server');
$proxy_port = intval(config_get('proxy_port'));
$proxy_userpwd = config_get('proxy_user').':'.config_get('proxy_pwd');
$proxy_type = config_get('proxy_type');
if ($proxy_type == 'https') {
$proxy_type = CURLPROXY_HTTPS;
} elseif ($proxy_type == 'sock4') {
$proxy_type = CURLPROXY_SOCKS4;
} elseif ($proxy_type == 'sock5') {
$proxy_type = CURLPROXY_SOCKS5;
} else {
$proxy_type = CURLPROXY_HTTP;
}
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_PROXY, $proxy_server);
curl_setopt($ch, CURLOPT_PROXYPORT, $proxy_port);
if ($proxy_userpwd != ':') {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_userpwd);
}
curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type);
curl_set_proxy($ch);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$httpheader[] = "Accept: */*";

View File

@@ -150,7 +150,6 @@ new Vue({
})
return {label: classList[key], children: tempList}
})
console.log(this.typeOption);
if(this.action == 'edit'){
Object.keys(info).forEach((key) => {
this.set[key] = info[key]
@@ -159,7 +158,9 @@ new Vue({
this.inputs = typeList[this.set.type].inputs;
this.note = typeList[this.set.type].note;
$.each(this.inputs, (name, item) => {
item.value = config[name];
if(typeof config[name] != 'undefined'){
item.value = config[name];
}
if(typeof item.value == 'undefined'){
if(item.type == 'checkbox'){
item.value = false;

View File

@@ -2,7 +2,7 @@
{block name="title"}SSL证书订单列表{/block}
{block name="main"}
<style>
tbody tr>td:nth-child(4){overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:180px;}
tbody tr>td:nth-child(5){overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:180px;}
.tips{cursor:pointer;}
textarea.form-control{margin-bottom: 3px;}
hr{margin-top: 10px;margin-bottom: 15px;border-top: 1px solid #eee;}
@@ -27,9 +27,25 @@ pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51
<option value="{$k}">{$v}</option>
{/foreach}</select>
</div>
<div class="form-group">
<select name="status" class="form-control"><option value="">所有状态</option><option value="0">待提交</option><option value="1">待验证</option><option value="2">正在验证</option><option value="5">失败</option><option value="3">已签发</option><option value="4">已吊销</option><option value="6">即将过期</option><option value="7">已过期</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>
<a href="/cert/order/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
<div class="btn-group">
<a href="/cert/order/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
<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="/cert/order/import">导入已有证书</a></li>
</ul>
</div>
<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="javascript:operation('delete')">删除</a></li><li><a href="javascript:operation('reset')">重置订单</a></li><li><a href="javascript:operation('open')">开启续签</a></li><li><a href="javascript:operation('close')">关闭续签</a></li></ul>
</div>
</form>
<table id="listTable">
@@ -59,6 +75,10 @@ $(document).ready(function(){
classes: 'table table-striped table-hover table-bordered',
uniqueId: 'id',
columns: [
{
field: '',
checkbox: true
},
{
field: 'id',
title: 'ID'
@@ -128,8 +148,28 @@ $(document).ready(function(){
} else if(value == 3) {
return '<span class="label label-success">已签发</span>';
} else if(value == 2) {
if(row.retrytime != null){
var now = new Date().getTime();
var retry = new Date(row.retrytime).getTime();
var diff = retry - now;
if(diff > 0){
var min = Math.floor(diff / 60000);
var sec = Math.floor((diff - min * 60000) / 1000);
return '<span title="'+min+'分'+sec+'秒后自动验证" data-toggle="tooltip" data-placement="top" class="label" style="background-color: #3e76fb;">正在验证</span>';
}
}
return '<span class="label" style="background-color: #3e76fb;">正在验证</span>';
} else if(value == 1) {
if(row.retrytime != null){
var now = new Date().getTime();
var retry = new Date(row.retrytime).getTime();
var diff = retry - now;
if(diff > 0){
var min = Math.floor(diff / 60000);
var sec = Math.floor((diff - min * 60000) / 1000);
return '<span title="'+min+'分'+sec+'秒后自动验证" data-toggle="tooltip" data-placement="top" class="label" style="background-color: #3e76fb;">待验证</span>';
}
}
return '<span class="label" style="background-color: #3e76fb;">待验证</span>';
} else if(value == 0) {
return '<span class="label label-info">待提交</span>';
@@ -142,6 +182,16 @@ $(document).ready(function(){
else if(value == -5) title = '验证订单失败';
else if(value == -6) title = '订单验证未通过';
else if(value == -7) title = '签发证书失败';
if(row.retrytime != null){
var now = new Date().getTime();
var retry = new Date(row.retrytime).getTime();
var diff = retry - now;
if(diff > 0){
var min = Math.floor(diff / 60000);
var sec = Math.floor((diff - min * 60000) / 1000);
return '<span title="'+min+'分'+sec+'秒后自动重试" data-toggle="tooltip" data-placement="top" class="label label-danger">'+title+'</span>'+(row.error?' <span onclick="showmsg(\''+row.error+'\')" class="tips" title="失败原因"><i class="fa fa-info-circle"></i></span>':'');
}
}
return '<span class="label label-danger">'+title+'</span>'+(row.error?' <span onclick="showmsg(\''+row.error+'\')" class="tips" title="失败原因"><i class="fa fa-info-circle"></i></span>':'');
}
}
@@ -381,5 +431,39 @@ function showLog(processid){
}
}, 'json');
}
function operation(action){
var rows = $("#listTable").bootstrapTable('getSelections');
if(rows.length == 0){
layer.msg('请选择要操作的订单');
return;
}
var ids = [];
for(var i in rows){
ids.push(rows[i].id);
}
if(action == 'delete'){
if(!confirm('确定要删除所选证书吗?删除后将无法再次下载')) return;
}else if(action == 'reset'){
if(!confirm('重置订单后,订单将变成待提交状态,是否确定重置?')) return;
}
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/cert/order/operation',
data : {action: action, 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});
}
}
});
}
</script>
{/block}

View File

@@ -8,6 +8,7 @@
<div class="panel-body">
<p><li>计划任务将以下命令添加到计划任务1分钟1次</li></p>
<p><code>cd {:app()->getRootPath()} && php think certtask</code></p>
<p><li>上次运行时间:<font color="green">{:config_get('certtask_time', '未运行', true)}</font></li></p>
</div>
</div>
@@ -64,6 +65,10 @@
<label class="col-sm-3 control-label">Telegram机器人通知</label>
<div class="col-sm-9"><select class="form-control" name="cert_notice_tgbot" default="{:config_get('cert_notice_tgbot')}"><option value="0">关闭</option><option value="1">开启</option><option value="2">开启(仅失败时)</option></select></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">群机器人Webhook</label>
<div class="col-sm-9"><select class="form-control" name="cert_notice_webhook" default="{:config_get('cert_notice_webhook')}"><option value="0">关闭</option><option value="1">开启</option><option value="2">开启(仅失败时)</option></select></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9"><input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/></div>
</div>

View File

@@ -4,6 +4,7 @@
<style>
.copy-btn{color:#52c41a;cursor:pointer;margin-right: 5px;}
.copy-btn:hover{color:#85ef79;}
.btn-refresh{margin-left:5px;font-size:10px;background-color:#6896cf}
tbody tr>td:nth-child(3){word-break:break-all;max-width:180px;}
tbody tr>td:nth-child(4){word-break:break-all;max-width:260px;}
</style>
@@ -120,7 +121,7 @@ $(document).ready(function(){
} else {
html += '<span class="label label-warning">未验证</span>';
}
html += '&nbsp;<a href="javascript:checkItem('+row.id+')" title="立即验证" class="btn btn-primary btn-xs"><i class="fa fa-refresh"></i></a>';
html += '<a href="javascript:checkItem('+row.id+')" title="立即验证" class="btn btn-primary btn-xs btn-refresh"><i class="fa fa-refresh"></i></a>';
return html;
}
},

View File

@@ -160,7 +160,9 @@ new Vue({
this.inputs = typeList[this.set.type].taskinputs;
this.note = typeList[this.set.type].tasknote;
$.each(this.inputs, (name, item) => {
item.value = config[name];
if(typeof config[name] != 'undefined'){
item.value = config[name];
}
if(typeof item.value == 'undefined'){
if(item.type == 'checkbox'){
item.value = false;
@@ -183,7 +185,7 @@ new Vue({
if(document.referrer.indexOf('&oid=') > 0){
var oid = document.referrer.split('&oid=')[1].split('&')[0];
if(oid){
$('select[name=oid]').val(20).trigger('change');
$('select[name=oid]').val(oid).trigger('change');
that.set.oid = oid;
}
}

View File

@@ -2,7 +2,7 @@
{block name="title"}SSL证书自动部署任务{/block}
{block name="main"}
<style>
tbody tr>td:nth-child(3){max-width:180px;}
tbody tr>td:nth-child(4){max-width:180px;}
.tips{cursor:pointer;}
pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51, 51, 51, 1);white-space: pre-line;color: rgba(236, 236, 236, 1)}
</style>
@@ -29,9 +29,16 @@ pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51
<option value="{$k}">{$v}</option>
{/foreach}</select>
</div>
<div class="form-group">
<select name="status" class="form-control"><option value="">所有状态</option><option value="0">待处理</option><option value="1">已完成</option><option value="-1">处理失败</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>
<a href="/cert/deploy/add" 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="javascript:operation('delete')">删除</a></li><li><a href="javascript:operation('reset')">重置任务</a></li><li><a href="javascript:operation('open')">开启任务</a></li><li><a href="javascript:operation('close')">停止任务</a></li></ul>
</div>
</form>
<table id="listTable">
@@ -61,6 +68,10 @@ $(document).ready(function(){
classes: 'table table-striped table-hover table-bordered',
uniqueId: 'id',
columns: [
{
field: '',
checkbox: true
},
{
field: 'id',
title: 'ID'
@@ -282,5 +293,39 @@ function showLog(processid){
}
}, 'json');
}
function operation(action){
var rows = $("#listTable").bootstrapTable('getSelections');
if(rows.length == 0){
layer.msg('请选择要操作的任务');
return;
}
var ids = [];
for(var i in rows){
ids.push(rows[i].id);
}
if(action == 'delete'){
if(!confirm('确定要删除所选自动部署任务吗?')) return;
}else if(action == 'reset'){
if(!confirm('重置任务后,任务将变成待处理状态,是否确定重置?')) return;
}
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/cert/deploy/operation',
data : {action: action, 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});
}
}
});
}
</script>
{/block}

View File

@@ -0,0 +1,167 @@
{extend name="common/layout" /}
{block name="title"}导入已有证书{/block}
{block name="main"}
<style>
.tips{color: #f6a838; padding-left: 5px;}
.control-label[is-required]:before {
content: "*";
color: #f56c6c;
margin-right: 4px;
}
.input-group-addon{padding: 6px 6px;}
</style>
<div class="row" id="app">
<div class="col-xs-12 center-block" style="float: none;">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title"><a href="/cert/certorder" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>导入已有证书</h3></div>
<div class="panel-body">
<form onsubmit="return false" method="post" class="form-horizontal" role="form" id="taskform">
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>证书内容</label>
<div class="col-sm-6">
<div class="input-group">
<textarea name="fullchain" v-model="set.fullchain" class="form-control" rows="5" placeholder="输入PEM格式证书链" required></textarea>
<a class="btn btn-default input-group-addon" @click="upload('fullchain')" title="上传证书文件"><i class="fa fa-upload"></i></a>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>私钥内容</label>
<div class="col-sm-6">
<div class="input-group">
<textarea name="privatekey" v-model="set.privatekey" class="form-control" rows="5" placeholder="输入PEM格式私钥" required></textarea>
<a class="btn btn-default input-group-addon" @click="upload('privatekey')" title="上传私钥文件"><i class="fa fa-upload"></i></a>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 col-xs-12 control-label no-padding-right" is-required>证书续期账户</label>
<div class="col-sm-6"><select name="aid" v-model="set.aid" class="form-control" required>
<option value="">--选择证书账户--</option>
{foreach $accounts as $k=>$v}
<option value="{$k}" data-type="{$v.type}">{$v.name}</option>
{/foreach}
</select></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>签名算法</label>
<div class="col-sm-6">
<label class="radio-inline" v-for="item in keytypeList">
<input type="radio" name="keytype" :value="item" v-model="set.keytype"> {{item}}
</label>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>密钥长度</label>
<div class="col-sm-6">
<label class="radio-inline" v-for="item in keysizeList">
<input type="radio" name="keysize" :value="item.value" v-model="set.keysize"> {{item.label}}
</label>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6"><button type="button" class="btn btn-primary" @click="submit">提交</button></div>
</div>
</form>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}vue/2.6.14/vue.min.js"></script>
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script src="/static/js/bootstrapValidator.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
type: '',
set: {
fullchain: '',
privatekey: '',
aid: '',
keytype: '',
keysize: '',
},
keytypeList: [
'RSA',
'ECC'
],
keysizeMap: [
{label:'2048 bit',value:'2048',type:'RSA'},
{label:'3072 bit',value:'3072',type:'RSA'},
{label:'P-256',value:'256',type:'ECC'},
{label:'P-384',value:'384',type:'ECC'},
],
keysizeList: [],
},
watch: {
'set.aid': function(val){
this.type = $('option:selected', 'select[name=aid]').data('type');
},
'set.keytype': function(val){
this.keysizeList = this.keysizeMap.filter((item) => {
return item.type == val;
})
if(!this.keysizeList.filter((item) => {return item.value == this.set.keysize}).length)
this.set.keysize = this.keysizeList[0].value;
},
},
mounted() {
this.set.keytype = 'RSA';
$("#taskform").bootstrapValidator({
live: 'submitted',
});
$('[data-toggle="tooltip"]').tooltip();
},
methods: {
submit(){
var that=this;
$("#taskform").data("bootstrapValidator").validate();
if(!$("#taskform").data("bootstrapValidator").isValid()){
return false;
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type: "POST",
url: "",
data: this.set,
dataType: 'json',
success: function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg, {icon: 1}, function(){
if(document.referrer.indexOf('/cert/certorder?') > 0)
window.location.href = document.referrer;
else
window.location.href = '/cert/certorder';
});
}else{
layer.alert(data.msg, {icon: 2});
}
},
error: function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
},
upload(name){
//读取上传文件并填充到表单
var file = document.createElement('input');
file.type = 'file';
file.accept = '.pem,.crt,.key';
file.style.display = 'none';
file.onchange = function(){
var reader = new FileReader();
reader.onload = function(e){
this.set[name] = e.target.result;
}.bind(this);
reader.readAsText(file.files[0]);
}.bind(this);
document.body.appendChild(file);
file.click();
}
},
});
</script>
{/block}

View File

@@ -103,7 +103,7 @@
{if request()->user['type'] eq 'user'}<li class="{:checkIfActive('index')}">
<a href="/"><i class="fa fa-home fa-fw"></i> <span>后台首页</span></a>
</li>{/if}
<li class="{:checkIfActive('domain,record,record_log,record_batch_add')}">
<li class="{:checkIfActive('domain,record,record_log,record_batch_add,domain_add,weight,record_batch_add2,record_batch_edit2,expire_notice')}">
<a href="/domain"><i class="fa fa-list-ul fa-fw"></i> <span>域名管理</span></a>
</li>
{if request()->user['level'] eq 2}
@@ -119,8 +119,8 @@
</span>
</a>
<ul class="treeview-menu">
<li><a href="/dmonitor/overview"><i class="fa fa-circle-o"></i> 运行概览</a></li>
<li><a href="/dmonitor/task"><i class="fa fa-circle-o"></i> 切换策略</a></li>
<li class="{:checkIfActive('overview')}"><a href="/dmonitor/overview"><i class="fa fa-circle-o"></i> 运行概览</a></li>
<li class="{:checkIfActive('task,taskform')}"><a href="/dmonitor/task"><i class="fa fa-circle-o"></i> 切换策略</a></li>
</ul>
</li>
<li class="treeview {:checkIfActive('opipset,opiplist,opipform')}">
@@ -132,11 +132,11 @@
</span>
</a>
<ul class="treeview-menu">
<li><a href="/optimizeip/opipset"><i class="fa fa-circle-o"></i> 优选设置</a></li>
<li><a href="/optimizeip/opiplist"><i class="fa fa-circle-o"></i> 任务管理</a></li>
<li class="{:checkIfActive('opipset')}"><a href="/optimizeip/opipset"><i class="fa fa-circle-o"></i> 优选设置</a></li>
<li class="{:checkIfActive('opiplist,opipform')}"><a href="/optimizeip/opiplist"><i class="fa fa-circle-o"></i> 任务管理</a></li>
</ul>
</li>
<li class="treeview {:checkIfActive('certaccount,account_form,certorder,order_form,deployaccount,deploytask,deploy_form,certset,cname')}">
<li class="treeview {:checkIfActive('certaccount,account_form,certorder,order_form,order_import,deployaccount,deploytask,deploy_form,certset,cname')}">
<a href="javascript:;">
<i class="fa fa-expeditedssl fa-fw"></i>
<span>SSL证书</span>
@@ -145,12 +145,12 @@
</span>
</a>
<ul class="treeview-menu">
<li><a href="/cert/certaccount"><i class="fa fa-circle-o"></i> SSL证书账户</a></li>
<li><a href="/cert/certorder"><i class="fa fa-circle-o"></i> SSL证书订单</a></li>
<li><a href="/cert/deployaccount"><i class="fa fa-circle-o"></i> 自动部署账户</a></li>
<li><a href="/cert/deploytask"><i class="fa fa-circle-o"></i> 自动部署任务</a></li>
<li><a href="/cert/cname"><i class="fa fa-circle-o"></i> CNAME代理</a></li>
<li><a href="/cert/certset"><i class="fa fa-circle-o"></i> 计划任务设置</a></li>
<li class="{:checkIfActive('certaccount')}"><a href="/cert/certaccount"><i class="fa fa-circle-o"></i> SSL证书账户</a></li>
<li class="{:checkIfActive('certorder,order_form,order_import')}"><a href="/cert/certorder"><i class="fa fa-circle-o"></i> SSL证书订单</a></li>
<li class="{:checkIfActive('deployaccount')}"><a href="/cert/deployaccount"><i class="fa fa-circle-o"></i> 自动部署账户</a></li>
<li class="{:checkIfActive('deploytask,deploy_form')}"><a href="/cert/deploytask"><i class="fa fa-circle-o"></i> 自动部署任务</a></li>
<li class="{:checkIfActive('cname')}"><a href="/cert/cname"><i class="fa fa-circle-o"></i> CNAME代理</a></li>
<li class="{:checkIfActive('certset')}"><a href="/cert/certset"><i class="fa fa-circle-o"></i> 计划任务设置</a></li>
</ul>
</li>
<li class="treeview {:checkIfActive('noticeset,proxyset')}">
@@ -162,8 +162,8 @@
</span>
</a>
<ul class="treeview-menu">
<li><a href="/system/noticeset"><i class="fa fa-circle-o"></i> 通知设置</a></li>
<li><a href="/system/proxyset"><i class="fa fa-circle-o"></i> 代理设置</a></li>
<li class="{:checkIfActive('noticeset')}"><a href="/system/noticeset"><i class="fa fa-circle-o"></i> 通知设置</a></li>
<li class="{:checkIfActive('proxyset')}"><a href="/system/proxyset"><i class="fa fa-circle-o"></i> 代理设置</a></li>
<li><a href="https://www.showdoc.com.cn/dnsmgr/11058996709621562" target="_blank" rel="noreferrer"><i class="fa fa-circle-o"></i> <span>接口文档</span></a></li>
</ul>
</li>

View File

@@ -1,222 +0,0 @@
{extend name="common/layout" /}
{block name="title"}容灾切换通知设置{/block}
{block name="main"}
<div class="row">
<div class="col-xs-12 col-sm-8 col-lg-6 center-block" style="float: none;">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">容灾切换通知设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form-horizontal" role="form">
<div class="form-group">
<label class="col-sm-3 control-label">邮件通知</label>
<div class="col-sm-9"><select class="form-control" name="notice_mail" default="{:config_get('notice_mail')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">微信公众号通知</label>
<div class="col-sm-9"><select class="form-control" name="notice_wxtpl" default="{:config_get('notice_wxtpl')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Telegram机器人通知</label>
<div class="col-sm-9"><select class="form-control" name="notice_tgbot" default="{:config_get('notice_tgbot')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9"><input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/></div>
</div>
</form>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">发信邮箱设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form-horizontal" role="form">
<div class="form-group">
<label class="col-sm-3 control-label">发信模式</label>
<div class="col-sm-9"><select class="form-control" name="mail_type" default="{:config_get('mail_type')}"><option value="0">SMTP发信</option><option value="1">搜狐Sendcloud</option><option value="2">阿里云邮件推送</option></select></div>
</div>
<div id="frame_set1">
<div class="form-group">
<label class="col-sm-3 control-label">SMTP服务器</label>
<div class="col-sm-9"><input type="text" name="mail_smtp" value="{:config_get('mail_smtp')}" class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">SMTP端口</label>
<div class="col-sm-9"><input type="text" name="mail_port" value="{:config_get('mail_port')}" class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">邮箱账号</label>
<div class="col-sm-9"><input type="text" name="mail_name" value="{:config_get('mail_name')}" class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">邮箱密码</label>
<div class="col-sm-9"><input type="text" name="mail_pwd" value="{:config_get('mail_pwd')}" class="form-control"/></div>
</div>
</div>
<div id="frame_set2">
<div class="form-group">
<label class="col-sm-3 control-label">API_USER</label>
<div class="col-sm-9"><input type="text" name="mail_apiuser" value="{:config_get('mail_apiuser')}" class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">API_KEY</label>
<div class="col-sm-9"><input type="text" name="mail_apikey" value="{:config_get('mail_apikey')}" class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">发信邮箱</label>
<div class="col-sm-9"><input type="text" name="mail_name2" value="{:config_get('mail_name')}" class="form-control"/></div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">收信邮箱</label>
<div class="col-sm-9"><input type="text" name="mail_recv" value="{:config_get('mail_recv')}" class="form-control" placeholder="不填默认为发信邮箱"/></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/>
<a href="javascript:mailtest()" class="btn btn-default btn-block">发送测试邮件</a>
</div>
</div>
</form>
</div>
<div class="panel-footer">
<span class="glyphicon glyphicon-info-sign"></span>
使用普通模式发信时建议使用QQ邮箱SMTP服务器smtp.qq.com端口465或587密码是QQ邮箱设置界面生成的<a href="https://service.mail.qq.com/detail/0/75" target="_blank" rel="noreferrer">授权码</a><br/>阿里云邮件推送:<a href="https://www.aliyun.com/product/directmail" target="_blank" rel="noreferrer">点此进入</a><a href="https://usercenter.console.aliyun.com/#/manage/ak" target="_blank" rel="noreferrer">获取AK/SK</a>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">微信公众号消息接口设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form-horizontal" role="form">
<div class="form-group">
<label class="col-sm-3 control-label">appToken</label>
<div class="col-sm-9"><input type="text" name="wechat_apptoken" value="{:config_get('wechat_apptoken')}" class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">用户UID</label>
<div class="col-sm-9"><input type="text" name="wechat_appuid" value="{:config_get('wechat_appuid')}" class="form-control"/></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9"><input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/></div>
</div>
</form>
</div>
<div class="panel-footer">
<b>WxPusher</b><a href="https://wxpusher.zjiecode.com/admin/" target="_blank" rel="noopener noreferrer">点此进入</a> ,注册并且创建应用 -> 将appToken填写到上方输入框 -> 扫码关注应用 -> 在用户列表查看自己的UID填写到上方输入框<br/>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">Telegram机器人接口设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form-horizontal" role="form">
<div class="form-group">
<label class="col-sm-3 control-label">Token</label>
<div class="col-sm-9"><input type="text" name="tgbot_token" value="{:config_get('tgbot_token')}" class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Chat Id</label>
<div class="col-sm-9"><input type="text" name="tgbot_chatid" value="{:config_get('tgbot_chatid')}" class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">使用代理服务器</label>
<div class="col-sm-9"><select class="form-control" name="tgbot_proxy" default="{:config_get('tgbot_proxy')}"><option value="0"></option><option value="1"></option></select></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/>
<a href="javascript:tgbottest()" class="btn btn-default btn-block">发送测试消息</a>
</div>
</div>
</form>
</div>
<div class="panel-footer">
<a href="https://t.me/BotFather" target="_blank" rel="noopener noreferrer">@BotFather</a>对话,使用/newbot命令创建一个新的机器人根据提示输入机器人的名称和用户名可得到Token或使用/mybots命令查看已创建的机器人<a href="https://t.me/getmyid_bot" target="_blank" rel="noopener noreferrer">@getmyid_bot</a>对话可得到Chat Id<br/>
</div>
</div>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script>
var items = $("select[default]");
for (i = 0; i < items.length; i++) {
$(items[i]).val($(items[i]).attr("default")||0);
}
$("select[name='mail_type']").change(function(){
if($(this).val() == 0){
$("#frame_set1").show();
$("#frame_set2").hide();
}else{
$("#frame_set1").hide();
$("#frame_set2").show();
}
});
$("select[name='mail_type']").change();
function saveSetting(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '',
data : $(obj).serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('设置保存成功!<br/>重启检测进程或容器后生效', {
icon: 1,
closeBtn: false
}, function(){
window.location.reload()
});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
function mailtest(){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'GET',
url : '/dmonitor/mailtest',
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg, {icon: 1});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
}
function tgbottest(){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'GET',
url : '/dmonitor/tgbottest',
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg, {icon: 1});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
}
</script>
{/block}

View File

@@ -56,6 +56,10 @@
<label class="col-sm-4 control-label">Telegram机器人通知</label>
<div class="col-sm-8"><select class="form-control" name="notice_tgbot" default="{:config_get('notice_tgbot')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">群机器人Webhook</label>
<div class="col-sm-8"><select class="form-control" name="notice_webhook" default="{:config_get('notice_webhook')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
</form>
</div>
<div class="modal-footer">

View File

@@ -1,110 +0,0 @@
{extend name="common/layout" /}
{block name="title"}容灾切换代理设置{/block}
{block name="main"}
<div class="row">
<div class="col-xs-12 col-sm-8 col-lg-6 center-block" style="float: none;">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">代理服务器设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form-horizontal" role="form">
<div class="form-group">
<label class="col-sm-3 control-label">代理IP</label>
<div class="col-sm-9"><input type="text" name="proxy_server" value="{:config_get('proxy_server')}" class="form-control"/></div>
</div><br/>
<div class="form-group">
<label class="col-sm-3 control-label">代理端口</label>
<div class="col-sm-9"><input type="text" name="proxy_port" value="{:config_get('proxy_port')}" class="form-control"/></div>
</div><br/>
<div class="form-group">
<label class="col-sm-3 control-label">代理账号</label>
<div class="col-sm-9"><input type="text" name="proxy_user" value="{:config_get('proxy_user')}" class="form-control" placeholder="没有请留空"/></div>
</div><br/>
<div class="form-group">
<label class="col-sm-3 control-label">代理密码</label>
<div class="col-sm-9"><input type="text" name="proxy_pwd" value="{:config_get('proxy_pwd')}" class="form-control" placeholder="没有请留空"/></div>
</div><br/>
<div class="form-group">
<label class="col-sm-3 control-label">代理协议</label>
<div class="col-sm-9"><select class="form-control" name="proxy_type" default="{:config_get('proxy_type')}">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
<option value="sock4">SOCK4</option>
<option value="sock5">SOCK5</option>
</select></div>
</div><br/>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9"><input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/><br/>
<a href="javascript:proxytest()" class="btn btn-default btn-block">测试连通性</a></div>
</div>
</form>
</div>
</div>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script>
var items = $("select[default]");
for (i = 0; i < items.length; i++) {
$(items[i]).val($(items[i]).attr("default")||0);
}
function saveSetting(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '',
data : $(obj).serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('设置保存成功!', {
icon: 1,
closeBtn: false
}, function(){
window.location.reload()
});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
function proxytest(){
var proxy_server = $("input[name='proxy_server']").val();
var proxy_port = $("input[name='proxy_port']").val();
var proxy_user = $("input[name='proxy_user']").val();
var proxy_pwd = $("input[name='proxy_pwd']").val();
var proxy_type = $("select[name='proxy_type']").val();
if(proxy_server=='' || proxy_port==''){
layer.alert('代理服务器和端口不能为空!');
return false;
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/dmonitor/proxytest',
data : {proxy_server:proxy_server, proxy_port:proxy_port, proxy_user:proxy_user, proxy_pwd:proxy_pwd, proxy_type:proxy_type},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('连通性测试成功!', {icon: 1})
}else{
layer.alert('连通性测试失败:'+data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
}
</script>
{/block}

View File

@@ -14,11 +14,16 @@ tbody tr>td:nth-child(2){overflow: hidden;text-overflow: ellipsis;white-space: n
<label>搜索</label>
<div class="form-group">
<select name="type" class="form-control"><option value="1">域名</option><option value="3">解析记录</option><option value="4">备用解析记录</option><option value="2">解析记录ID</option><option value="5">备注</option></select>
</div>
</div>
</div>
<div class="form-group">
<input type="text" class="form-control" name="kw" placeholder="">
</div>
<div class="form-group">
<div class="form-group">
<select name="status" class="form-control"><option value="">健康状况</option><option value="0">正常</option><option value="1">异常</option></select>
</div>
</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>
<a href="/dmonitor/task/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>

View File

@@ -17,13 +17,16 @@
<form onsubmit="return false" method="post" class="form-horizontal" role="form" id="taskform">
<div class="form-group">
<label class="col-sm-3 col-xs-12 control-label no-padding-right">域名选择</label>
<div class="col-sm-3 col-xs-5"><input type="text" name="rr" v-model="set.rr" placeholder="主机记录" class="form-control" required></div>
<div class="col-sm-3 col-xs-7 dselect"><select name="did" v-model="set.did" class="form-control" required>
<option value="">--主域名--</option>
{foreach $domains as $k=>$v}
<option value="{$k}">{$v}</option>
{/foreach}
</select></div>
<div class="col-sm-6">
<div class="input-group">
<input type="text" name="rr" v-model="set.rr" placeholder="主机记录" class="form-control" required>
<span class="input-group-addon">.</span>
<select name="did" v-model="set.did" class="form-control" required>
<option value="">--主域名--</option>
<option v-for="option in domainList" :value="option.id">{{option.name}}</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">解析记录</label>
@@ -50,6 +53,15 @@
<input type="text" name="backup_value" v-model="set.backup_value" placeholder="支持填写IPv4或CNAME地址" class="form-control" required>
</div>
</div>
<div class="form-group" v-show="set.type==2&&dnstype=='cloudflare'">
<div class="col-sm-offset-3 col-sm-7">
<div class="checkbox">
<label>
<input type="checkbox" name="name" v-model="set.cdn"> 切换时同时开启Cloudflare代理模式
</label>
</div>
</div>
</div>
<div class="form-group" v-show="set.type<=2">
<label class="col-sm-3 control-label no-padding-right">检测协议</label>
<div class="col-sm-6">
@@ -131,6 +143,7 @@
<script>
var action = '{$action}';
var info = {$info|json_encode|raw};
var domainList = {$domains|json_encode|raw};
var support_ping = '{$support_ping}';
new Vue({
el: '#app',
@@ -153,7 +166,10 @@ new Vue({
timeout: 2,
cycle: 3,
proxy: 0,
cdn: 0,
},
dnstype: null,
domainList: domainList,
recordList: [],
typeList: [
{value:0, label:'无操作'},
@@ -176,6 +192,10 @@ new Vue({
if(typeof record.Value == 'object') this.set.main_value = record.Value[0];
else this.set.main_value = record.Value;
}
},
'set.did': function(val){
if(val == '') return;
this.dnstype = this.domainList.find(item => item.id == val).type;
}
},
mounted() {

View File

@@ -36,10 +36,18 @@
<input type="text" class="form-control" name="sk" required>
</div>
</div>
<div class="form-group">
<div class="form-group" id="ext_name_div" style="display:none;">
<label class="col-sm-3 control-label no-padding-right" id="ext_name">扩展字段</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="ext" placeholder="没有请勿填写">
<input type="text" class="form-control" name="ext" placeholder="">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" id="ext_name">使用代理</label>
<div class="col-sm-9">
<label class="radio-inline"><input type="radio" name="proxy" value="0">
</label><label class="radio-inline"><input type="radio" name="proxy" value="1">
</label>
</div>
</div>
<div class="form-group">
@@ -137,8 +145,12 @@ $(document).ready(function(){
if(dnsconfig[type] == undefined) return;
$("#ak_name").html(dnsconfig[type].config.ak);
$("#sk_name").html(dnsconfig[type].config.sk);
if(dnsconfig[type].config.ext == undefined) dnsconfig[type].config.ext = '扩展字段';
else $("#ext_name").html(dnsconfig[type].config.ext);
if(dnsconfig[type].config.ext == undefined){
$("#ext_name_div").hide();
}else{
$("#ext_name_div").show();
$("#ext_name").html(dnsconfig[type].config.ext);
}
});
})
function addframe(){
@@ -149,6 +161,7 @@ function addframe(){
$("#form-store input[name=ak]").val('');
$("#form-store input[name=sk]").val('');
$("#form-store input[name=ext]").val('');
$("#form-store input[name=proxy]").eq(0).prop('checked',true);
$("#form-store input[name=remark]").val('');
$("select[name=type]").change();
}
@@ -170,6 +183,7 @@ function editframe(id){
$("#form-store input[name=ak]").val(data.data.ak);
$("#form-store input[name=sk]").val(data.data.sk);
$("#form-store input[name=ext]").val(data.data.ext);
$("#form-store input[name=proxy]").eq(data.data.proxy).prop('checked',true);
$("#form-store input[name=remark]").val(data.data.remark);
$("select[name=type]").change();
}else{

View File

@@ -49,7 +49,7 @@
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6"><button type="button" class="btn btn-primary" onclick="save()">保存</button></div>
<div class="col-sm-offset-3 col-sm-6"><button type="button" class="btn btn-primary" onclick="save()">添加</button></div>
</div>
</form>
</div>
@@ -110,7 +110,6 @@ function save(){
if(!$("#form-store").data("bootstrapValidator").isValid()){
return;
}
var act = $("#form-store input[name=action]").val();
var ii = layer.load(2);
$.ajax({
type : 'POST',

View File

@@ -0,0 +1,162 @@
{extend name="common/layout" /}
{block name="title"}批量添加解析{/block}
{block name="main"}
<style>
tbody tr>td:nth-child(3){min-width:300px;word-break:break-all;}
</style>
<div class="row" id="app">
<div class="col-xs-12 center-block" style="float: none;">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title"><a href="/domain" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>批量添加解析</h3></div>
<div class="panel-body">
<form onsubmit="return false" method="post" class="form-horizontal" role="form" id="form-store">
<div class="form-group">
<label class="col-sm-3 col-xs-12 control-label no-padding-right">主机记录&记录值</label>
<div class="col-sm-6">
<textarea name="record" v-model="set.record" placeholder="主机记录和记录值用空格隔开,一行一个" class="form-control" rows="8" required></textarea>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">记录类型</label>
<div class="col-sm-6">
<select name="type" class="form-control" v-model="set.type">
<option value="">A / CNAME / AAAA 自动识别</option>
<option value="A">A</option>
<option value="CNAME">CNAME</option>
<option value="AAAA">AAAA</option>
<option value="NS">NS</option>
<option value="MX">MX</option>
<option value="SRV">SRV</option>
<option value="TXT">TXT</option>
<option value="CAA">CAA</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">线路类型</label>
<div class="col-sm-6">
<select name="line" class="form-control" disabled><option value="default">默认</option></select>
</div>
</div>
<div class="form-group" style="display:none" id="mx_type">
<label class="col-sm-3 control-label no-padding-right">MX优先级</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="mx" v-model="set.mx">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">TTL</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="ttl" v-model="set.ttl" placeholder="指解析结果在DNS服务器中的缓存时间" required>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6"><button type="button" class="btn btn-primary" @click="submit">确定添加</button></div>
</div>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">解析记录添加结果</h3></div>
<div class="panel-body">
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>ID</th>
<th>域名</th>
<th>添加结果</th>
</tr>
</thead>
<tbody>
<tr v-for="item in domainList">
<td>{{item.id}}</td>
<td><img :src="'/static/images/'+item.type+'.ico'" class="type-logo"></img><a :href="'/record/'+item.id" target="_blank">{{item.name}}</a></td>
<td v-html="item.result"></td>
</tr>
</tbody>
</table>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}vue/2.6.14/vue.min.js"></script>
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script>
new Vue({
el: '#app',
data: {
domainList: [],
set: {
record: '',
type: '',
mx: 10,
ttl: 600,
}
},
watch: {
'set.type': function(val){
if(val == 'MX'){
$("#mx_type").show();
}else{
$("#mx_type").hide();
}
}
},
mounted() {
this.domainList = JSON.parse(sessionStorage.getItem('domains')) || [];
if(this.domainList.length == 0){
layer.alert('请先选中要添加解析的域名', {icon: 2}, function(){
window.location.href = '/domain';
});
}
for(var i=0; i<this.domainList.length; i++){
this.$set(this.domainList[i], 'result', '<span class="text-muted">待添加</span>');
}
},
methods: {
async save(id){
var that = this;
return new Promise((resolve, reject) => {
$.ajax({
type: "POST",
url: '/record/batchadd/'+id,
data: that.set,
dataType: 'json',
success: function(data) {
resolve(data);
},
error: function() {
reject('服务器错误');
}
});
});
},
async submit(){
if(this.set.record == ''){
layer.alert('请填写主机记录和记录值', {icon: 2});
return;
}
if(this.set.ttl == ''){
layer.alert('请填写TTL', {icon: 2});
return;
}
if(this.set.type == 'MX' && this.set.mx == ''){
layer.alert('请填写MX优先级', {icon: 2});
return;
}
var ii = layer.load(2);
for(var i=0; i<this.domainList.length; i++){
this.domainList[i].result = '<span class="text-yellow"><i class="fa fa-refresh fa-spin fa-fw"></i> 正在添加</span>';
var res = await this.save(this.domainList[i].id);
if(res.code == 0){
this.domainList[i].result = '<span class="text-green">'+res.msg+'</span>';
}else{
this.domainList[i].result = '<span class="text-red">'+res.msg+'</span>';
}
}
layer.close(ii);
}
},
});
</script>
{/block}

View File

@@ -0,0 +1,157 @@
{extend name="common/layout" /}
{block name="title"}批量修改解析{/block}
{block name="main"}
<style>
tbody tr>td:nth-child(3){min-width:300px;word-break:break-all;}
</style>
<div class="row" id="app">
<div class="col-xs-12 center-block" style="float: none;">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title"><a href="/domain" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>批量修改解析</h3></div>
<div class="panel-body">
<form onsubmit="return false" method="post" class="form-horizontal" role="form" id="form-store">
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">主机记录</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="name" v-model="set.name" placeholder="填写已有的主机记录" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">记录类型</label>
<div class="col-sm-6">
<select name="type" class="form-control" v-model="set.type">
<option value="A">A</option>
<option value="CNAME">CNAME</option>
<option value="AAAA">AAAA</option>
<option value="NS">NS</option>
<option value="MX">MX</option>
<option value="SRV">SRV</option>
<option value="TXT">TXT</option>
<option value="CAA">CAA</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">线路类型</label>
<div class="col-sm-6">
<select name="line" class="form-control" disabled><option value="default">默认</option></select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">记录值</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="value" v-model="set.value" placeholder="输入记录值" required>
</div>
</div>
<div class="form-group" style="display:none" id="mx_type">
<label class="col-sm-3 control-label no-padding-right">MX优先级</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="mx" v-model="set.mx" placeholder="留空则不修改">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">TTL</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="ttl" v-model="set.ttl" placeholder="留空则不修改">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6"><button type="button" class="btn btn-primary" @click="submit">确定修改</button></div>
</div>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">解析记录修改结果</h3></div>
<div class="panel-body">
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>ID</th>
<th>域名</th>
<th>修改结果</th>
</tr>
</thead>
<tbody>
<tr v-for="item in domainList">
<td>{{item.id}}</td>
<td><img :src="'/static/images/'+item.type+'.ico'" class="type-logo"></img><a :href="'/record/'+item.id" target="_blank">{{item.name}}</a></td>
<td v-html="item.result"></td>
</tr>
</tbody>
</table>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}vue/2.6.14/vue.min.js"></script>
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script>
new Vue({
el: '#app',
data: {
domainList: [],
set: {
id: '',
name: '',
type: 'A',
value: '',
mx: '',
ttl: '',
}
},
mounted() {
this.domainList = JSON.parse(sessionStorage.getItem('domains')) || [];
if(this.domainList.length == 0){
layer.alert('请先选中要修改解析的域名', {icon: 2}, function(){
window.location.href = '/domain';
});
}
for(var i=0; i<this.domainList.length; i++){
this.$set(this.domainList[i], 'result', '<span class="text-muted">待修改</span>');
}
},
methods: {
async save(id){
var that = this;
return new Promise((resolve, reject) => {
that.set.id = id;
$.ajax({
type: "POST",
url: '/record/batchedit',
data: that.set,
dataType: 'json',
success: function(data) {
resolve(data);
},
error: function() {
reject('服务器错误');
}
});
});
},
async submit(){
if(this.set.name == ''){
layer.alert('请填写主机记录', {icon: 2});
return;
}
if(this.set.value == ''){
layer.alert('请填写记录值', {icon: 2});
return;
}
var ii = layer.load(2);
for(var i=0; i<this.domainList.length; i++){
this.domainList[i].result = '<span class="text-yellow"><i class="fa fa-refresh fa-spin fa-fw"></i> 正在修改</span>';
var res = await this.save(this.domainList[i].id);
if(res.code == 0){
this.domainList[i].result = '<span class="text-green">'+res.msg+'</span>';
}else{
this.domainList[i].result = '<span class="text-red">'+res.msg+'</span>';
}
}
layer.close(ii);
}
},
});
</script>
{/block}

View File

@@ -12,7 +12,6 @@
</div>
<div class="modal-body">
<form class="form-horizontal" id="form-store">
<input type="hidden" name="recordcount"/>
<div class="form-group">
<label class="col-sm-3 control-label">域名账户</label>
<div class="col-sm-9">
@@ -29,6 +28,7 @@
<select name="domain" id="domainList" class="form-control"></select>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@@ -50,6 +50,18 @@
<div class="modal-body">
<form class="form-horizontal" id="form-store2">
<input type="hidden" name="id"/>
<div class="form-group">
<label class="col-sm-3 control-label">到期提醒</label>
<div class="col-sm-9">
<div class="input-group">
<select name="is_notice" class="form-control">
<option value="0"></option>
<option value="1"></option>
</select>
<a tabindex="0" class="input-group-addon" role="button" data-toggle="popover" data-trigger="focus" title="" data-placement="bottom" data-content="域名到期提醒,其他设置在“到期提醒设置”里面" data-original-title="说明"><span class="glyphicon glyphicon-info-sign"></span></a>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">是否隐藏</label>
<div class="col-sm-9">
@@ -104,9 +116,17 @@
<option value="{$k}">{$v}</option>
{/foreach}</select>
</div>
<div class="form-group">
<select name="status" class="form-control"><option value="">所有状态</option><option value="1">即将到期</option><option value="2">已到期</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>{/if}
{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>
</div>
<a href="/domain/expirenotice" class="btn btn-default">到期提醒设置</a>{/if}
</form>
<table id="listTable">
@@ -136,7 +156,12 @@ $(document).ready(function(){
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bordered',
uniqueId: 'id',
columns: [
{
field: '',
checkbox: true
},
{
field: 'id',
title: 'ID'
@@ -164,12 +189,61 @@ $(document).ready(function(){
title: '添加时间'
},
{
field: 'remark',
title: '备注'
field: 'regtime',
title: '注册时间',
visible: false,
formatter: function(value, row, index) {
var html = '';
if(value == null) {
if (row.checkstatus == 0) {
html = '<font color="#bdbdbd">待查询</font>';
} else if (row.checkstatus == 2) {
html = '<font color="#bdbdbd">查询失败</font>';
}
} else {
html = value.slice(0,10);
}
return html;
}
},
{
field: 'expiretime',
title: '到期时间',
formatter: function(value, row, index) {
var html = '';
if(value == null) {
if (row.checkstatus == 0) {
html = '<font color="#bdbdbd">待查询</font>';
} else if (row.checkstatus == 2) {
html = '<font color="#bdbdbd">查询失败</font>';
}
} else {
var now = new Date().getTime();
var expiretime = new Date(value).getTime();
var days = parseInt((expiretime - now) / 1000 / 24 / 60 / 60);
if (days > 30) {
html += '<span title="还有'+days+'天到期" data-toggle="tooltip" data-placement="bottom">'+value.slice(0,10)+'</span>';
} else if (days > 0) {
html += '<b><span class="text-yellow" title="还有'+days+'天到期" data-toggle="tooltip" data-placement="bottom">'+value.slice(0,10)+'</span></b>';
} else {
html += '<b><span class="text-red" title="已到期" data-toggle="tooltip" data-placement="bottom">'+value.slice(0,10)+'</span></b>';
}
}
html += '&nbsp;<a href="javascript:updateDate('+row.id+')" title="刷新到期时间" class="text-green"><i class="fa fa-refresh"></i></a>';
return html;
}
},
{
field: 'is_notice',
title: '到期提醒',
formatter: function(value, row, index) {
return value==1?'<font color="green">是</font>':'<font color="blue">否</font>';
}
},
{
field: 'is_hide',
title: '是否隐藏',
visible: false,
formatter: function(value, row, index) {
return value==1?'<font color="grey">是</font>':'<font color="blue">否</font>';
}
@@ -177,10 +251,15 @@ $(document).ready(function(){
{
field: 'is_sso',
title: '对接开关',
visible: false,
formatter: function(value, row, index) {
return value==1?'<font color="green">是</font>':'<font color="red">否</font>';
}
},
{
field: 'remark',
title: '备注'
},
{
field: '',
title: '操作',
@@ -193,18 +272,16 @@ $(document).ready(function(){
return html;
}
},
]
],
onLoadSuccess: function(data) {
$('[data-toggle="tooltip"]').tooltip()
}
})
$("#form-store select[name=aid]").change(function(){
getDomainList()
})
$('#domainList').on('select2:select', function (e) {
var data = e.params.data;
$("#form-store input[name=recordcount]").val(data.recordcount);
});
$('[data-toggle="popover"]').popover()
})
function addframe(){
@@ -222,7 +299,7 @@ function saveAdd(){
}
var name = select[0].text;
var thirdid = select[0].id;
var recordcount = $("#form-store input[name=recordcount]").val();
var recordcount = select[0].recordcount;
if(aid=='' || thirdid==''){
layer.alert('请确保各项不能为空!');return false;
}
@@ -250,25 +327,13 @@ function saveAdd(){
});
}
function editframe(id){
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/domain/op/act/get',
data : {id: id},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
$("#modal-store2").modal('show');
$("#form-store2 input[name=id]").val(data.data.id);
$("#form-store2 select[name=is_hide]").val(data.data.is_hide);
$("#form-store2 select[name=is_sso]").val(data.data.is_sso);
$("#form-store2 input[name=remark]").val(data.data.remark);
}else{
layer.alert(data.msg, {icon: 2})
}
}
});
var row = $("#listTable").bootstrapTable('getRowByUniqueId', id);
$("#modal-store2").modal('show');
$("#form-store2 input[name=id]").val(row.id);
$("#form-store2 select[name=is_hide]").val(row.is_hide);
$("#form-store2 select[name=is_sso]").val(row.is_sso);
$("#form-store2 select[name=is_notice]").val(row.is_notice);
$("#form-store2 input[name=remark]").val(row.remark);
}
function saveEdit(){
var ii = layer.load(2);
@@ -280,14 +345,9 @@ function saveEdit(){
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg,{
icon: 1,
closeBtn: false
}, function(){
layer.closeAll();
$("#modal-store2").modal('hide');
searchRefresh();
});
layer.msg(data.msg, {icon:1, time:800});
$("#modal-store2").modal('hide');
searchRefresh();
}else{
layer.alert(data.msg, {icon: 2})
}
@@ -342,9 +402,9 @@ function getDomainList(){
params.page = params.page || 1
if(data.code == 0){
var resultData = [];
for (var i = 0; i < data.data.list.length; i++) {
resultData.push({'id': data.data.list[i].DomainId, 'text': data.data.list[i].Domain, 'recordcount': data.data.list[i].RecordCount});
}
$.each(data.data.list, function(index, item){
resultData.push({'id': item.DomainId, 'text': item.Domain + (item.disabled ? ' (已添加)' : ''), 'recordcount': item.RecordCount, disabled:item.disabled});
})
return {
results: resultData,
pagination: {
@@ -362,6 +422,124 @@ function getDomainList(){
cache:false
});
}
function operation(action){
var rows = $("#listTable").bootstrapTable('getSelections');
if(rows.length == 0){
layer.msg('请选择要操作的域名');
return;
}
var ids = [];
$.each(rows, function(index, item){
ids.push(item.id);
})
if(action == 'editremark'){
batch_edit_remark(ids)
return;
}else if(action == 'addrecord'){
sessionStorage.setItem('domains', JSON.stringify(rows));
window.location.href = '/record/batchadd';
return;
}else if(action == 'editrecord'){
sessionStorage.setItem('domains', JSON.stringify(rows));
window.location.href = '/record/batchedit';
return;
}else if(action == 'delete'){
var confirmobj = layer.confirm('确定要删除所选域名吗?', {
btn: ['确定','取消']
}, function(){
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/domain/op/act/batchdel',
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);
$.ajax({
type : 'POST',
url : '/domain/op/act/batchsetnotice',
data : {ids: ids, is_notice: is_notice},
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 batch_edit_remark(ids) {
layer.open({
type: 1,
area: ['350px'],
closeBtn: 2,
title: '批量修改备注',
content: '<div style="padding:15px"><div class="form-group"><input class="form-control" type="text" name="remark2" value="" autocomplete="off" placeholder="备注信息"></div></div>',
btn: ['确认', '取消'],
yes: function(){
var remark = $("input[name='remark2']").val();
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/domain/op/act/batchedit',
data : {ids:ids, remark:remark},
dataType : 'json',
success : function(data) {
layer.close(ii);
layer.alert(data.msg,{
icon: 1,
closeBtn: false
}, function(){
layer.closeAll();
searchRefresh();
});
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
}
});
}
function updateDate(id){
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/domain/updatedate',
data : {id: id},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.msg('刷新成功', {icon: 1, time: 600});
searchRefresh();
}else{
layer.alert(data.msg, {icon: 2});
}
}
});
}
function loading(){
layer.load(2);
}

View File

@@ -0,0 +1,158 @@
{extend name="common/layout" /}
{block name="title"}批量添加域名{/block}
{block name="main"}
<div class="row" id="app">
<div class="col-xs-12 col-sm-10 col-md-8 col-lg-6 center-block" style="float: none;">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title"><a href="/domain" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>批量添加域名</h3></div>
<div class="panel-body">
<form onsubmit="return false" method="post" class="form-horizontal" role="form" id="form-store">
<div class="form-group">
<label class="col-sm-3 control-label">域名账户</label>
<div class="col-sm-9">
<div class="input-group">
<select name="aid" class="form-control" v-model="aid">
{foreach $accounts as $k=>$v}
<option value="{$k}">{$v}</option>
{/foreach}
</select>
<div class="input-group-btn">
<button type="button" @click="getDomainList" class="btn btn-info">获取域名</button>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">选择域名</label>
<div class="col-sm-9">
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th><input type="checkbox" v-model="checkall"></th>
<th>域名</th>
</tr>
</thead>
<tbody>
<tr v-for="item in domainList">
<td><input type="checkbox" name="domain[]" :value="item.DomainId" v-model="item.checked" :disabled="item.disabled"></td>
<td><span :title="item.DomainId">{{item.Domain}}</span><font color="#888" v-if="item.disabled"> (已添加)</font></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9"><button type="button" class="btn btn-primary" @click="submit">确定添加</button></div>
</div>
</form>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}vue/2.6.14/vue.min.js"></script>
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script>
new Vue({
el: '#app',
data: {
aid: '',
domainList: [],
page: 1,
pagesize: 10,
checkall: false,
},
watch: {
aid: function(val){
this.domainList = [];
},
checkall: function(val){
this.domainList.forEach(function(item){
item.checked = val&&!item.disabled;
});
}
},
mounted() {
this.aid = '{$accounts|@key}';
},
methods: {
async getDomainListPaged(){
var that = this;
return new Promise((resolve, reject) => {
$.ajax({
type: "POST",
url: "/domain/list",
data: {aid: that.aid, page: that.page, pagesize: that.pagesize},
dataType: 'json',
success: function(data) {
if(data.code == 0){
resolve(data.data);
}else{
reject(data.msg);
}
}
});
});
},
async getDomainList(){
this.domainList = [];
while(true){
try{
layer.msg('正在获取第'+this.page+'页域名', {icon: 16, shade: 0.01});
var data = await this.getDomainListPaged();
if(data.total == 0 || data.list.length == 0){
layer.closeAll();
break;
}
this.domainList = this.domainList.concat(data.list);
if(this.domainList.length >= data.total){
layer.closeAll();
break;
}
this.page++;
}catch(e){
layer.alert(e, {icon: 2});
break;
}
}
},
submit(){
var domains = [];
this.domainList.forEach(function(item){
if(item.checked && !item.disabled){
domains.push({name: item.Domain, id: item.DomainId, recordcount:item.RecordCount});
}
});
if(this.aid == ''){
layer.alert('请选择域名账户');
return;
}
if(domains.length == 0){
layer.alert('请选择要添加的域名');
return;
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type: "POST",
url: "/domain/op/act/batchadd",
data: {aid: this.aid, domains: domains},
dataType: 'json',
success: function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg, {icon: 1}, function(){
window.location.href = '/domain';
});
}else{
layer.alert(data.msg, {icon: 2});
}
},
error: function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
}
},
});
</script>
{/block}

View File

@@ -0,0 +1,85 @@
{extend name="common/layout" /}
{block name="title"}域名到期提醒设置{/block}
{block name="main"}
<div class="row">
<div class="col-xs-12 col-sm-8 col-lg-6 center-block" style="float: none;">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title"><a href="/domain" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>域名到期提醒设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form-horizontal" role="form">
<div class="form-group">
<label class="col-sm-3 control-label">到期提醒天数</label>
<div class="col-sm-9"><input type="text" name="expire_noticedays" value="{:config_get('expire_noticedays')}" class="form-control" placeholder="留空则不开启到期提醒"/><font color="green">域名到期前多少天发送通知可填写多个天数用英文逗号隔开。例如填写7,14则在域名到期前7天与14天分别发送通知。</font></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">邮件通知</label>
<div class="col-sm-9"><select class="form-control" name="expire_notice_mail" default="{:config_get('expire_notice_mail')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">微信公众号通知</label>
<div class="col-sm-9"><select class="form-control" name="expire_notice_wxtpl" default="{:config_get('expire_notice_wxtpl')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Telegram机器人通知</label>
<div class="col-sm-9"><select class="form-control" name="expire_notice_tgbot" default="{:config_get('expire_notice_tgbot')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">群机器人Webhook</label>
<div class="col-sm-9"><select class="form-control" name="expire_notice_webhook" default="{:config_get('expire_notice_webhook')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/>
</div>
</div>
</form>
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title">计划任务说明</h3></div>
<div class="panel-body">
<p>支持域名到期提醒+域名列表到期时间自动刷新。与SSL证书共用计划任务不需要单独添加计划任务。</p><p><a href="/cert/certset">查看计划任务说明</a></p>
</div>
</div>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script>
var items = $("select[default]");
for (i = 0; i < items.length; i++) {
$(items[i]).val($(items[i]).attr("default")||0);
}
function saveSetting(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '',
data : $(obj).serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('设置保存成功!', {
icon: 1,
closeBtn: false
}, function(){
window.location.reload()
});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
</script>
{/block}

View File

@@ -17,6 +17,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
<form class="form-horizontal" id="form-store">
<input type="hidden" name="action"/>
<input type="hidden" name="recordid"/>
<input type="hidden" name="recordinfo"/>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">主机记录</label>
<div class="col-sm-9">
@@ -158,7 +159,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
</div>
<div class="row">
<div class="col-xs-12 center-block" style="float: none;">
<div class="panel panel-default panel-default">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{if request()->user['type'] eq 'user'}<a href="/domain" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>{/if}{$domainName}</h3>
</div>
@@ -176,6 +177,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
<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>
<a href="javascript:addframe()" class="btn btn-success"><i class="fa fa-plus"></i> 添加记录</a>
{if $dnsconfig.type=='aliyun'}<a href="/record/weight/{$domainId}" class="btn btn-default">权重配置</a>{/if}
<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="/record/batchadd/{$domainId}">添加</a></li><li><a href="javascript:operation('open')">启用</a></li><li><a href="javascript:operation('pause')">暂停</a></li><li><a href="javascript:operation('edit')">修改记录</a></li><li><a href="javascript:operation('editline')">修改线路</a></li>{if $dnsconfig.remark == 1}<li><a href="javascript:operation('editremark')">修改备注</a></li>{/if}<li><a href="javascript:operation('delete')">删除</a></li></ul>
@@ -210,9 +212,6 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
<div class="form-group">
<input type="text" class="form-control" name="value" placeholder="输入记录值">
</div>
<div class="form-group">
<select name="status" class="form-control"><option value="">所有状态</option><option value="1">启用</option><option value="0">暂停</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>
<a href="javascript:advanceSearch()" class="btn"><i class="fa fa-angle-up"></i> 收起</a>
@@ -236,7 +235,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
var recordLine = {$recordLine|json_encode|raw};
var dnsconfig = {$dnsconfig|json_encode|raw};
var defaultLine = recordLine[0].id;
var sidePagination = dnsconfig.type == 'baidu' || dnsconfig.type == 'namesilo' ? 'client' : 'server';
var sidePagination = dnsconfig.page ? 'client' : 'server';
var showWeight = dnsconfig.weight;
$(document).ready(function(){
updateToolbar();
@@ -423,6 +422,7 @@ function editframe(recordid){
$("#modal-title").html("修改记录");
$("#form-store input[name=action]").val("update");
$("#form-store input[name=recordid]").val(recordid);
$("#form-store input[name=recordinfo]").val(JSON.stringify(row));
$("#form-store input[name=name]").val(row.Name);
$("#form-store select[name=type]").val(row.Type);
$("#form-store select[name=type]").change();
@@ -470,11 +470,12 @@ function save(){
});
}
function setStatus(recordid, status){
var row = $("#listTable").bootstrapTable('getRowByUniqueId', recordid);
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/record/status/{$domainId}',
data : {recordid: recordid, status: status},
data : {recordid: recordid, status: status, recordinfo: JSON.stringify(row)},
dataType : 'json',
success : function(data) {
layer.close(ii);
@@ -489,6 +490,7 @@ function setStatus(recordid, status){
});
}
function delItem(recordid) {
var row = $("#listTable").bootstrapTable('getRowByUniqueId', recordid);
var confirmobj = layer.confirm('确定要删除此解析记录吗?', {
btn: ['确定','取消']
}, function(){
@@ -496,7 +498,7 @@ function delItem(recordid) {
$.ajax({
type : 'POST',
url : '/record/delete/{$domainId}',
data : {recordid: recordid},
data : {recordid: recordid, recordinfo: JSON.stringify(row)},
dataType : 'json',
success : function(data) {
layer.close(ii);
@@ -555,32 +557,16 @@ function operation(action){
return;
}
if(action == 'edit'){
var records = [];
$.each(rows, function(index, item){
records.push({recordid:item.RecordId, name:item.Name, line:item.Line, mx:item.MX, ttl:item.TTL, weight:item.Weight, remark:item.Remark});
})
batch_edit(records)
batch_edit(rows)
return;
}else if(action == 'editline'){
var records = [];
$.each(rows, function(index, item){
records.push({recordid:item.RecordId, name:item.Name, type:item.Type, value:item.Value, mx:item.MX, ttl:item.TTL, weight:item.Weight, remark:item.Remark});
})
batch_edit_line(records)
batch_edit_line(rows)
return;
}else if(action == 'editremark'){
var ids = [];
$.each(rows, function(index, item){
ids.push(item.RecordId);
})
batch_edit_remark(ids)
batch_edit_remark(rows)
return;
}
var ids = [];
$.each(rows, function(index, item){
ids.push(item.RecordId);
})
var confirmobj = layer.confirm('确定要'+(action=='open'?'启用':(action=='pause'?'暂停':'删除'))+'所选记录吗?', {
btn: ['确定','取消']
}, function(){
@@ -588,7 +574,7 @@ function operation(action){
$.ajax({
type : 'POST',
url : '/record/batch/{$domainId}',
data : {action: action, recordids: ids},
data : {action: action, recordinfo: JSON.stringify(rows)},
dataType : 'json',
success : function(data) {
layer.close(ii);
@@ -645,10 +631,10 @@ function batch_save(){
}
});
}
function batch_edit_line(records){
$("#batch_num").text(records.length);
function batch_edit_line(rows){
$("#batch_num").text(rows.length);
$("#modal-store3").modal('show');
$("#form-store3 input[name=recordinfo]").val(JSON.stringify(records));
$("#form-store3 input[name=recordinfo]").val(JSON.stringify(rows));
initLine('', 'line_list3');
}
function batch_save_line(){
@@ -675,7 +661,7 @@ function batch_save_line(){
}
});
}
function batch_edit_remark(recordids) {
function batch_edit_remark(rows) {
layer.open({
type: 1,
area: ['350px'],
@@ -689,7 +675,7 @@ function batch_edit_remark(recordids) {
$.ajax({
type : 'POST',
url : '/record/batch/{$domainId}',
data : {action:'remark', recordids:recordids, remark:remark},
data : {action:'remark', recordinfo: JSON.stringify(rows), remark:remark},
dataType : 'json',
success : function(data) {
layer.close(ii);

250
app/view/domain/weight.html Normal file
View File

@@ -0,0 +1,250 @@
{extend name="common/layout" /}
{block name="title"}权重配置 - {$domainName}{/block}
{block name="main"}
<style>
.table-bordered>tbody>tr>td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:200px;vertical-align:middle;}
</style>
<div class="modal" id="modal-store" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static">
<div class="modal-dialog">
<div class="modal-content animated flipInX">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span
aria-hidden="true">&times;</span><span
class="sr-only">Close</span></button>
<h4 class="modal-title" id="modal-title">设置权重</h4>
</div>
<div class="modal-body">
<form class="form-horizontal" id="form-store" onsubmit="return save(this)">
<input type="hidden" name="id"/>
<input type="hidden" name="subdomain"/>
<input type="hidden" name="type"/>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">选择线路</label>
<div class="col-sm-9">
<select name="line" id="line" class="form-control" onchange="changeLine()"></select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">线路权重开关</label>
<div class="col-sm-9">
<div class="material-switch" style="padding-top:7px"><input id="weight-switch" name="status" type="checkbox" value="1" checked onchange="changeOpen()"/><label for="weight-switch" class="label-primary"></label></div>
</div>
</div>
<table class="table table-striped table-hover table-bordered">
<thead>
<tr><th>主机记录</th><th>记录类型</th><th>记录值</th><th>权重</th></tr>
</thead>
<tbody id="weight-list">
<tr><td colspan="4" class="text-center">正在加载...</td></tr>
</tbody>
</table>
<span class="text-muted">提示权重范围为数字0-100</span>
<div class="form-group">
<div class="col-sm-12 text-right">
<button type="submit" class="btn btn-primary">保存</button>
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 center-block" style="float: none;">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title"><a href="/record/{$domainId}" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>权重配置 - {$domainName}</h3></div>
<div class="panel-body">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline" id="searchToolbar">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="keyword" placeholder="子域名">
</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>
</form>
<table id="listTable">
</table>
</div>
</div>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.21.4/bootstrap-table.min.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.21.4/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
var dnsconfig = {$dnsconfig|json_encode|raw};
var recordLine = {$recordLine|json_encode|raw};
var domainId = {$domainId};
var weightList = [];
var lineList = [];
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 15;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/record/weight/data/{$domainId}',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bordered',
uniqueId: 'id',
columns: [
{
field: 'SubDomain',
title: '子域名'
},
{
field: 'Type',
title: '记录类型'
},
{
field: 'RecordCount',
title: '记录数量'
},
{
field: 'Open',
title: '权重配置状态',
formatter: function(value, row, index) {
if(value == 1){
return '<font color="green"><i class="fa fa-check-circle"></i>已开启</font>';
}else{
return '<font color="#b5bbc8"><i class="fa fa-pause-circle"></i>已关闭</font>';
}
}
},
{
field: '',
title: '操作',
formatter: function(value, row, index) {
var html = '';
if(row.Open == 1){
if(row.Type == 'CNAME'){
html += '<a class="btn btn-warning btn-xs" title="CNAME类型解析默认必须开权重模式每次解析应答只返回一条解析结果记录值" disabled>关闭权重</a>&nbsp;&nbsp;';
}else{
html += '<a href="javascript:setWeightStatus(\''+row.SubDomain+'\', \'0\')" class="btn btn-warning btn-xs">关闭权重</a>&nbsp;&nbsp;';
}
}else{
html += '<a href="javascript:setWeightStatus(\''+row.SubDomain+'\', \'1\')" class="btn btn-success btn-xs">开启权重</a>&nbsp;&nbsp;';
}
html += '<a href="javascript:editframe('+row.id+')" class="btn btn-primary btn-xs">设置权重</a>';
return html;
}
},
],
})
})
function editframe(id){
var row = $("#listTable").bootstrapTable('getRowByUniqueId', id);
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/record/list',
data : {id: domainId, rr: row.rr},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
$("#modal-store").modal('show');
$("#modal-title").html("设置权重 - "+row.SubDomain);
$("#form-store input[name=id]").val(id);
$("#form-store input[name=subdomain]").val(row.SubDomain);
$("#form-store input[name=type]").val(row.Type);
lineList = [];
$.each(recordLine, function(i, item){
if(data.data.find(x => x.Line == item.id)){
item.open = row.Open;
if(row.LineAlgorithms && row.LineAlgorithms.LineAlgorithm.length > 0){
var tmpLine = row.LineAlgorithms.LineAlgorithm.find(x => x.Line == item.id);
if(tmpLine) item.open = tmpLine.Open;
}
lineList.push(item);
}
});
$("#line").empty();
$.each(lineList, function(i, item){
$("#line").append('<option value="'+item.id+'">'+item.name+'</option>');
});
weightList = data.data;
changeLine();
}else{
layer.alert(data.msg, {icon: 2})
}
}
});
}
function changeLine(){
var line = $("#line").val();
$("#weight-switch").prop("checked", lineList.find(x => x.id == line).open);
$("#weight-list").empty();
$.each(weightList, function(i, item){
if(item.Line == line){
$("#weight-list").append('<tr><td>'+item.Name+'</td><td>'+item.Type+'</td><td>'+item.Value+'</td><td><input type="number" class="form-control" name="weight['+item.RecordId+']" value="'+item.Weight+'" style="width:80px;" min="0" max="100"/></td></tr>');
}
});
changeOpen();
}
function changeOpen(){
if($("#weight-switch").is(':checked')){
$("#weight-list input[name^='weight']").prop("disabled", false);
}else{
$("#weight-list input[name^='weight']").prop("disabled", true);
}
}
function save(){
if($("#form-store input[name=username]").val()==''){
layer.alert('请确保各项不能为空!');return false;
}
var act = $("#form-store input[name=action]").val();
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/record/weight/{$domainId}/act/update',
data : $("#form-store").serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg,{
icon: 1,
closeBtn: false
}, function(){
layer.closeAll();
$("#modal-store").modal('hide');
searchRefresh();
});
}else{
layer.alert(data.msg, {icon: 2})
}
}
});
return false;
}
function setWeightStatus(subdomain, status){
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/record/weight/{$domainId}/act/status',
data : {subdomain: subdomain, status: status},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.closeAll();
searchRefresh();
}else{
layer.alert(data.msg, {icon: 2});
}
}
});
}
</script>
{/block}

View File

@@ -31,12 +31,12 @@
<div class="small-box bg-green">
<div class="inner">
<h3 id="count2">0</h3>
<p>用户数量</p>
<p>容灾切换策略</p>
</div>
<div class="icon">
<i class="fa fa-users"></i>
<i class="fa fa-heartbeat"></i>
</div>
<a href="/user" class="small-box-footer">More info <i class="fa fa-arrow-circle-right"></i></a>
<a href="/dmonitor/task" class="small-box-footer">More info <i class="fa fa-arrow-circle-right"></i></a>
</div>
</div>
<!-- ./col -->
@@ -45,12 +45,12 @@
<div class="small-box bg-yellow">
<div class="inner">
<h3 id="count3">0</h3>
<p>解析数量</p>
<p>SSL证书订单</p>
</div>
<div class="icon">
<i class="fa fa-globe"></i>
<i class="fa fa-expeditedssl"></i>
</div>
<a href="#" class="small-box-footer">More info <i class="fa fa-arrow-circle-right"></i></a>
<a href="/cert/certorder" class="small-box-footer">More info <i class="fa fa-arrow-circle-right"></i></a>
</div>
</div>
<!-- ./col -->
@@ -59,31 +59,76 @@
<div class="small-box bg-red">
<div class="inner">
<h3 id="count4">0</h3>
<p>DNS平台数量</p>
<p>SSL部署任务</p>
</div>
<div class="icon">
<i class="fa fa-connectdevelop"></i>
</div>
<a href="#" class="small-box-footer">More info <i class="fa fa-arrow-circle-right"></i></a>
<a href="/cert/deploytask" class="small-box-footer">More info <i class="fa fa-arrow-circle-right"></i></a>
</div>
</div>
<!-- ./col -->
</div>
<!-- /.row -->
<div class="row">
<div class="col-md-7 col-sm-12">
<div id="browser-notice"></div>
<div class="col-md-12">
<div id="browser-notice"></div>
</div>
<div class="col-md-4 col-sm-12">
<div class="box box-primary">
<div class="box-header with-border">
<i class="fa fa-heartbeat fa-fw"></i>
<h3 class="box-title">容灾切换概览</h3>
</div>
<ul class="nav nav-pills nav-stacked">
<li><a href="/dmonitor/overview">运行状态<b><span class="pull-right" id="dmonitor_state"></span></b></a></li>
<li><a href="/dmonitor/task">切换策略(已开启)<span class="pull-right badge bg-blue" id="dmonitor_active">0</span></a>
</li>
<li><a href="/dmonitor/task?status=0">健康状况正常<span class="pull-right badge bg-green" id="dmonitor_status_0">0</span></a></li>
<li><a href="/dmonitor/task?status=1">健康状况异常<span class="pull-right badge bg-red" id="dmonitor_status_1">0</span></a></li>
</ul>
</div>
<div class="box box-info">
<div class="box-header with-border">
<i class="fa fa-globe fa-fw"></i>
<h3 class="box-title">CF优选IP概览</h3>
</div>
<ul class="nav nav-pills nav-stacked">
<li><a href="/optimizeip/opiplist">任务数量(已开启)<span class="pull-right badge bg-blue" id="optimizeip_active">0</span></a>
</li>
<li><a href="/optimizeip/opiplist?status=1">更新成功<span class="pull-right badge bg-green" id="optimizeip_status_1">0</span></a></li>
<li><a href="/optimizeip/opiplist?status=2">更新失败<span class="pull-right badge bg-red" id="optimizeip_status_2">0</span></a></li>
</ul>
</div>
</div>
<div class="col-md-4 col-sm-12">
<div class="box box-success">
<div class="box-header with-border">
<i class="fa fa-globe fa-fw"></i>
<h3 class="box-title">SSL证书概览</h3>
</div>
<ul class="nav nav-pills nav-stacked">
<li class="text-center"><a href="/cert/certorder"><b>SSL证书订单</b></a></li>
<li><a href="/cert/certorder?status=3">已签发<span class="pull-right badge bg-green" id="certorder_status_3">0</span></a>
</li>
<li><a href="/cert/certorder?status=5">签发失败<span class="pull-right badge bg-red" id="certorder_status_5">0</span></a></li>
<li><a href="/cert/certorder?status=6">即将过期<span class="pull-right badge bg-orange" id="certorder_status_6">0</span></a></li>
<li><a href="/cert/certorder?status=7">已过期<span class="pull-right badge bg-gray" id="certorder_status_7">0</span></a></li>
<li class="text-center"><a href="/cert/deploytask"><b>自动部署任务</b></a></li>
<li><a href="/cert/deploytask?status=0">待处理<span class="pull-right badge bg-aqua" id="certdeploy_status_0">0</span></a></li>
<li><a href="/cert/deploytask?status=1">部署成功<span class="pull-right badge bg-green" id="certdeploy_status_1">0</span></a></li>
<li><a href="/cert/deploytask?status=-1">部署失败<span class="pull-right badge bg-red" id="certdeploy_status_2">0</span></a></li>
</ul>
</div>
</div>
<div class="col-md-4 col-sm-12">
<div class="box box-primary">
<div class="box-header with-border">
<i class="fa fa-cloud"></i>
<h3 class="box-title">服务器信息</h3>
</div>
<table class="table table-bordered">
<tbody>
<tr>
<td class="query-title">框架版本</td>
<td class="query-result">{$info.framework_version}</td>
</tr>
<tr>
<td class="query-title">PHP版本</td>
<td class="query-result">{$info.php_version}</td>
@@ -92,14 +137,6 @@
<td class="query-title">MySQL版本</td>
<td class="query-result">{$info.mysql_version}</td>
</tr>
<tr>
<td class="query-title">WEB软件</td>
<td class="query-result">{$info.software}</td>
</tr>
<tr>
<td class="query-title">操作系统</td>
<td class="query-result">{$info.os}</td>
</tr>
<tr>
<td class="query-title">服务器时间</td>
<td class="query-result">{$info.date}</td>
@@ -107,8 +144,6 @@
</tbody>
</table>
</div>
</div>
<div class="col-md-5 col-sm-12">
<div class="box box-default">
<div class="box-header with-border">
<i class="fa fa-volume-up"></i>
@@ -131,9 +166,28 @@ $(document).ready(function(){
dataType : 'json',
success : function(data) {
$('#count1').html(data.domains);
$('#count2').html(data.users);
$('#count3').html(data.records);
$('#count4').html(data.types);
$('#count2').html(data.tasks);
$('#count3').html(data.certs);
$('#count4').html(data.deploys);
$('#dmonitor_state').html(data.dmonitor_state==1?'<font color="green">正在运行</font>':'<font color="red">已停止</font>');
$('#dmonitor_active').html(data.dmonitor_active);
$('#dmonitor_status_0').html(data.dmonitor_status_0);
$('#dmonitor_status_1').html(data.dmonitor_status_1);
$('#optimizeip_active').html(data.optimizeip_active);
$('#optimizeip_status_1').html(data.optimizeip_status_1);
$('#optimizeip_status_2').html(data.optimizeip_status_2);
$('#certorder_status_3').html(data.certorder_status_3);
$('#certorder_status_5').html(data.certorder_status_5);
$('#certorder_status_6').html(data.certorder_status_6);
$('#certorder_status_7').html(data.certorder_status_7);
$('#certdeploy_status_0').html(data.certdeploy_status_0);
$('#certdeploy_status_1').html(data.certdeploy_status_1);
$('#certdeploy_status_2').html(data.certdeploy_status_2);
$('.badge').each(function() {
if ($(this).text().trim() === '0') {
$(this).css('opacity', '0.4');
}
});
$.ajax({
url: '{$checkupdate}',
type: 'get',

View File

@@ -16,14 +16,20 @@
<div class="panel-body">
<form onsubmit="return false" method="post" class="form-horizontal" role="form" id="taskform">
<div class="form-group">
<label class="col-sm-3 col-xs-12 control-label no-padding-right">域名选择</label>
<div class="col-sm-3 col-xs-5"><input type="text" name="rr" v-model="set.rr" placeholder="主机记录" class="form-control" required></div>
<div class="col-sm-3 col-xs-7 dselect"><select name="did" v-model="set.did" class="form-control" required>
<option value="">--主域名--</option>
{foreach $domains as $k=>$v}
<option value="{$k}">{$v}</option>
{/foreach}
</select></div>
<label class="col-sm-3 control-label no-padding-right">域名选择</label>
<div class="col-sm-6">
<div class="input-group">
<input type="text" name="rr" v-model="set.rr" placeholder="主机记录" class="form-control" required>
<span class="input-group-addon">.</span>
<select name="did" v-model="set.did" class="form-control" required>
<option value="">--主域名--</option>
{foreach $domains as $k=>$v}
<option value="{$k}">{$v}</option>
{/foreach}
</select>
<a tabindex="0" class="input-group-addon" role="button" data-toggle="popover" data-trigger="focus" title="" data-placement="bottom" data-content="不支持对CloudFlare里的域名添加优选必须使用其他DNS服务商。" data-original-title="说明"><span class="glyphicon glyphicon-info-sign"></span></a>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">CDN服务商</label>
@@ -142,6 +148,7 @@ new Vue({
live: 'submitted',
});
$('[data-toggle="tooltip"]').tooltip();
$('[data-toggle="popover"]').popover()
},
methods: {
submit(){

View File

@@ -14,11 +14,16 @@ tbody tr>td:nth-child(2){overflow: hidden;text-overflow: ellipsis;white-space: n
<label>搜索</label>
<div class="form-group">
<select name="type" class="form-control"><option value="1">域名</option><option value="2">备注</option></select>
</div>
</div>
</div>
<div class="form-group">
<input type="text" class="form-control" name="kw" placeholder="">
</div>
<div class="form-group">
<div class="form-group">
<select name="status" class="form-control"><option value="">更新结果</option><option value="1">成功</option><option value="2">失败</option></select>
</div>
</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>
<a href="/optimizeip/opipform/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>

View File

@@ -13,6 +13,7 @@
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title">使用说明</h3></div>
<div class="panel-body">
<p><li>不支持对CloudFlare里的域名添加优选必须使用其他DNS服务商。需开通Cloudflare for SaaS且域名使用CNAME的方式解析到CloudFlare。</li></p>
<p><li>数据接口:<a href="https://www.wetest.vip/" target="_blank" rel="noreferrer">wetest.vip</a> 数据接口支持CloudFlare、CloudFront、Gcore<a href="https://stock.hostmonit.com/" target="_blank" rel="noreferrer">HostMonit</a> 只支持CloudFlare。</li></p>
<p><li>接口密钥默认o1zrmHAF为免费KEY可永久免费使用。</li></p>
<p><li>计划任务将以下命令添加到计划任务周期设置为15分钟以上</li></p>

View File

@@ -94,9 +94,13 @@
<div class="col-sm-9"><input type="text" name="tgbot_chatid" value="{:config_get('tgbot_chatid')}" class="form-control"/></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">使用代理服务器</label>
<div class="col-sm-9"><select class="form-control" name="tgbot_proxy" default="{:config_get('tgbot_proxy')}"><option value="0"></option><option value="1"></option></select></div>
</div>
<label class="col-sm-3 control-label">使用代理服务器</label>
<div class="col-sm-9"><select class="form-control" name="tgbot_proxy" default="{:config_get('tgbot_proxy')}"><option value="0"></option><option value="1"></option><option value="2">自定义反代URL</option></select></div>
</div>
<div class="form-group" id="tgbot_url_div" style="display:none;">
<label class="col-sm-3 control-label">自定义反代URL</label>
<div class="col-sm-9"><input type="text" name="tgbot_url" value="{:config_get('tgbot_url')}" class="form-control" placeholder="默认为https://api.telegram.org"/></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/>
@@ -109,6 +113,26 @@
<a href="https://t.me/BotFather" target="_blank" rel="noopener noreferrer">@BotFather</a>对话,使用/newbot命令创建一个新的机器人根据提示输入机器人的名称和用户名可得到Token或使用/mybots命令查看已创建的机器人<a href="https://t.me/getmyid_bot" target="_blank" rel="noopener noreferrer">@getmyid_bot</a>对话可得到Chat Id<br/>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">群机器人Webhook</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form-horizontal" role="form">
<div class="form-group">
<label class="col-sm-3 control-label">Webhook地址</label>
<div class="col-sm-9"><input type="text" name="webhook_url" value="{:config_get('webhook_url')}" class="form-control"/></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/>
<a href="javascript:webhooktest()" class="btn btn-default btn-block">发送测试消息</a>
</div>
</div>
</form>
</div>
<div class="panel-footer">
仅支持填写企业微信、钉钉、飞书群机器人的Webhook地址
</div>
</div>
</div>
</div>
{/block}
@@ -128,7 +152,15 @@ $("select[name='mail_type']").change(function(){
$("#frame_set2").show();
}
});
$("select[name='tgbot_proxy']").change(function(){
if($(this).val() == 2){
$("#tgbot_url_div").show();
}else{
$("#tgbot_url_div").hide();
}
});
$("select[name='mail_type']").change();
$("select[name='tgbot_proxy']").change();
function saveSetting(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
@@ -139,7 +171,7 @@ function saveSetting(obj){
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('设置保存成功!<br/>重启检测进程或容器后生效', {
layer.alert('设置保存成功!<br/>如有使用容灾切换,重启检测进程后生效', {
icon: 1,
closeBtn: false
}, function(){
@@ -196,5 +228,25 @@ function tgbottest(){
}
});
}
function webhooktest(){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'GET',
url : '/system/webhooktest',
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg, {icon: 1});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
}
</script>
{/block}

Some files were not shown because too many files have changed in this diff Show More