diff --git a/config/config.go b/config/config.go
index 7e365663c..ca65c304f 100755
--- a/config/config.go
+++ b/config/config.go
@@ -39,7 +39,7 @@ type DnsConfig struct {
URL string
NetInterface string
Cmd string
- IPv6Reg string // ipv6匹配正则表达式
+ Ipv6Reg string // ipv6匹配正则表达式
Domains []string
}
DNS DNS
@@ -291,10 +291,10 @@ func (conf *DnsConfig) getIpv6AddrFromInterface() string {
for _, netInterface := range ipv6 {
if netInterface.Name == conf.Ipv6.NetInterface && len(netInterface.Address) > 0 {
- if conf.Ipv6.IPv6Reg != "" {
+ if conf.Ipv6.Ipv6Reg != "" {
// 匹配第几个IPv6
- if match, err := regexp.MatchString("@\\d", conf.Ipv6.IPv6Reg); err == nil && match {
- num, err := strconv.Atoi(conf.Ipv6.IPv6Reg[1:])
+ if match, err := regexp.MatchString("@\\d", conf.Ipv6.Ipv6Reg); err == nil && match {
+ num, err := strconv.Atoi(conf.Ipv6.Ipv6Reg[1:])
if err == nil {
if num > 0 {
if num <= len(netInterface.Address) {
@@ -303,14 +303,14 @@ func (conf *DnsConfig) getIpv6AddrFromInterface() string {
util.Log("未找到第 %d 个IPv6地址! 将使用第一个IPv6地址", num)
return netInterface.Address[0]
}
- util.Log("IPv6匹配表达式 %s 不正确! 最小从1开始", conf.Ipv6.IPv6Reg)
+ util.Log("IPv6匹配表达式 %s 不正确! 最小从1开始", conf.Ipv6.Ipv6Reg)
return ""
}
}
// 正则表达式匹配
- util.Log("IPv6将使用正则表达式 %s 进行匹配", conf.Ipv6.IPv6Reg)
+ util.Log("IPv6将使用正则表达式 %s 进行匹配", conf.Ipv6.Ipv6Reg)
for i := 0; i < len(netInterface.Address); i++ {
- matched, err := regexp.MatchString(conf.Ipv6.IPv6Reg, netInterface.Address[i])
+ matched, err := regexp.MatchString(conf.Ipv6.Ipv6Reg, netInterface.Address[i])
if matched && err == nil {
util.Log("匹配成功! 匹配到地址: ", netInterface.Address[i])
return netInterface.Address[i]
diff --git a/main.go b/main.go
index e69277963..412021493 100644
--- a/main.go
+++ b/main.go
@@ -162,8 +162,6 @@ func runWebServer() error {
http.HandleFunc("/save", web.BasicAuth(web.Save))
http.HandleFunc("/logs", web.BasicAuth(web.Logs))
http.HandleFunc("/clearLog", web.BasicAuth(web.ClearLog))
- http.HandleFunc("/ipv4NetInterface", web.BasicAuth(web.Ipv4NetInterfaces))
- http.HandleFunc("/ipv6NetInterface", web.BasicAuth(web.Ipv6NetInterfaces))
http.HandleFunc("/webhookTest", web.BasicAuth(web.WebhookTest))
util.Log("监听 %s", *listen)
diff --git a/static/common.css b/static/common.css
index 99881135d..8d6e4fea0 100644
--- a/static/common.css
+++ b/static/common.css
@@ -146,11 +146,11 @@ main {
margin-right: 25px;
}
-[data-theme='dark'] .theme-button:hover {
+.theme-button:hover {
box-shadow: 0px 0px 15px #0d0d0dab;
}
-[data-theme='dark'] .theme-button:active {
+.theme-button:active {
transform: scale(0.98);
}
diff --git a/static/constant.js b/static/constant.js
new file mode 100644
index 000000000..2481f061d
--- /dev/null
+++ b/static/constant.js
@@ -0,0 +1,265 @@
+const DNS_PROVIDERS = {
+ alidns: {
+ name: {
+ "en": "Aliyun",
+ "zh-cn": "阿里云",
+ },
+ idLabel: "AccessKey ID",
+ secretLabel: "AccessKey Secret",
+ helpHtml: {
+ "en": "Create AccessKey ",
+ "zh-cn": "创建 AccessKey ",
+ }
+ },
+ tencentcloud: {
+ name: {
+ "en": "Tencent",
+ "zh-cn": "腾讯云",
+ },
+ idLabel: "SecretId",
+ secretLabel: "SecretKey",
+ helpHtml: {
+ "en": "Create AccessKey ",
+ "zh-cn": "创建腾讯云 API 密钥 ",
+ }
+ },
+ dnspod: {
+ name: {
+ "en": "DnsPod",
+ },
+ idLabel: "ID",
+ secretLabel: "Token",
+ helpHtml: {
+ "en": "Create Token ",
+ "zh-cn": "创建 DNSPod Token ",
+ }
+ },
+ cloudflare: {
+ name: {
+ "en": "Cloudflare",
+ },
+ idLabel: "",
+ secretLabel: "Token",
+ helpHtml: {
+ "en": "Create Token -> Edit Zone DNS (Use template) ",
+ "zh-cn": "创建令牌 -> 编辑区域 DNS (使用模板) ",
+ }
+ },
+ huaweicloud: {
+ name: {
+ "en": "Huawei",
+ "zh-cn": "华为云",
+ },
+ idLabel: "Access Key Id",
+ secretLabel: "Secret Access Key",
+ helpHtml: {
+ "en": "Create ",
+ "zh-cn": "新增访问密钥 ",
+ }
+ },
+ callback: {
+ name: {
+ "en": "Callback",
+ },
+ idLabel: "URL",
+ secretLabel: "RequestBody",
+ helpHtml: {
+ "en": "Callback Support variables #{ip}, #{domain}, #{recordType}, #{ttl}",
+ "zh-cn": "自定义回调 支持的变量 #{ip}, #{domain}, #{recordType}, #{ttl}",
+ }
+ },
+ baiducloud: {
+ name: {
+ "en": "Baidu",
+ "zh-cn": "百度云",
+ },
+ idLabel: "AccessKey ID",
+ secretLabel: "AccessKey Secret",
+ helpHtml: {
+ "en": "Create AccessKey Apply for a ticket DDNS needs to call the API, and the related APIs of Baidu Cloud are only open to users who have applied. Please submit a ticket before using it.",
+ "zh-cn": "创建 AccessKey 申请工单 DDNS 需调用 API ,而百度云相关 API 仅对申请用户开放,使用前请先提交工单申请。",
+ }
+ },
+ porkbun: {
+ name: {
+ "en": "Porkbun",
+ },
+ idLabel: "API Key",
+ secretLabel: "Secret Key",
+ helpHtml: {
+ "en": "Create Access ",
+ "zh-cn": "创建 Access ",
+ }
+ },
+ godaddy: {
+ name: {
+ "en": "GoDaddy",
+ },
+ idLabel: "Key",
+ secretLabel: "Secret",
+ helpHtml: {
+ "en": "Create API KEY ",
+ "zh-cn": "创建 API KEY ",
+ }
+ },
+ googledomain: {
+ name: {
+ "en": "Google Domain",
+ },
+ idLabel: "Username",
+ secretLabel: "Password",
+ helpHtml: {
+ "en": "How to get started ",
+ "zh-cn": "新建动态域名解析记录 ",
+ }
+ },
+ namecheap: {
+ name: {
+ "en": "Namecheap",
+ },
+ idLabel: "",
+ secretLabel: "Password",
+ helpHtml: {
+ "en": "How to get started Namecheap DDNS does not support updating IPv6 ",
+ "zh-cn": "开启namecheap动态域名解析 Namecheap DDNS 不支持更新 IPv6 ",
+ }
+ },
+ namesilo: {
+ name: {
+ "en": "NameSilo",
+ },
+ idLabel: "",
+ secretLabel: "Password",
+ helpHtml: {
+ "en": "How to get started Please note that the TTL of namesilo is at least 1 hour ",
+ "zh-cn": "开启namesilo动态域名解析 请注意namesilo的TTL最低1小时 ",
+ }
+ },
+};
+
+const SVG_CODE = {
+ success: ` `,
+ info: ` `,
+ warning: ' ',
+ error: ' '
+}
+
+
+const I18N_MAP = {
+ 'en': {
+ 'Logs': 'Logs',
+ 'Save': 'Save',
+ 'Config:': 'Config:',
+ 'Add': 'Add',
+ 'Delete': 'Delete',
+ 'DNS Provider': 'DNS Provider',
+ 'Create AccessKey': 'Create AccessKey',
+ 'Auto': 'Auto',
+ '1s': '1s',
+ '5s': '5s',
+ '10s': '10s',
+ '1m': '1m',
+ '2m': '2m',
+ '10m': '10m',
+ '30m': '30m',
+ '1h': '1h',
+ 'ttlHelp': 'You can modify it if the account supports a smaller TTL. The TTL will only be updated when the IP changes',
+ 'Enabled': 'Enabled',
+ 'Get IP method': 'Get IP method',
+ 'By api': 'By api',
+ 'By network card': 'By network card',
+ 'By command': 'By command',
+ 'domainsHelp': `
+ One domain per line, you can use colon to separate the root domain
+ (example.cn.eu.org) and the subdomain (www), fill in as: www:example.cn.eu.org
+ `,
+ 'Regular exp.': 'Regular exp.',
+ 'regHelp': 'You can use @1 to specify the first IPv6 address, @2 to specify the second IPv6 address... You can also use regular expressions to match the specified IPv6 address, leave it blank to disable it',
+ 'Others': 'Others',
+ 'Deny from WAN': 'Deny from WAN',
+ 'NotAllowWanAccessHelp': 'Default enabled, can prohibit access to this page from the public network',
+ 'Username': 'Username',
+ 'accountHelp': 'Please enter to protect your information security',
+ 'Password': 'Password',
+ 'WebhookURLHelp': `
+ Click to get more info
+ Support variables #{ipv4Addr}, #{ipv4Result},
+ #{ipv4Domains}, #{ipv6Addr}, #{ipv6Result}, #{ipv6Domains}
+ `,
+ 'WebhookRequestBodyHelp': 'If RequestBody is empty, it is a GET request, otherwise it is a POST request. Supported variables are the same as above',
+ 'WebhookHeadersHelp': 'One header per line, such as: Authorization: Bearer API_KEY',
+ 'Try it': 'Try it',
+ 'Clear': 'Clear',
+ 'OK': 'OK',
+ "Ipv4UrlHelp": "https://myip4.ipip.net, https://ddns.oray.com/checkip, https://ip.3322.net",
+ "Ipv6UrlHelp": "https://speed.neu6.edu.cn/getIP.php, https://v6.ident.me, https://6.ipw.cn",
+ "Ipv4NetInterfaceHelp": "Get IPv4 address through network card",
+ "Ipv6NetInterfaceHelp": "If you do not specify a matching regular expression, the first IPv6 address will be used by default",
+ "Ipv4CmdHelp": "Get IPv4 through command, only use the first matching IPv4 address of standard output(stdout). Such as: ip -4 addr show eth1",
+ "Ipv6CmdHelp": "Get IPv6 through command, only use the first matching IPv6 address of standard output(stdout). Such as: ip -6 addr show eth1",
+ "NetInterfaceEmptyHelp": 'No available network card found ',
+ },
+ 'zh-cn': {
+ 'Logs': '日志',
+ 'Save': '保存',
+ 'Config:': '配置切换:',
+ 'Add': '添加',
+ 'Delete': '删除',
+ 'DNS Provider': 'DNS服务商',
+ 'Create AccessKey': '创建 AccessKey',
+ 'Auto': '自动',
+ '1s': '1秒',
+ '5s': '5秒',
+ '10s': '10秒',
+ '1m': '1分钟',
+ '2m': '2分钟',
+ '10m': '10分钟',
+ '30m': '30分钟',
+ '1h': '1小时',
+ 'ttlHelp': '如账号支持更小的 TTL, 可修改。IP 有变化时才会更新TTL',
+ 'Enabled': '是否启用',
+ 'Get IP method': '获取 IP 方式',
+ 'By api': '通过接口获取',
+ 'By network card': '通过网卡获取',
+ 'By command': '通过命令获取',
+ 'domainsHelp': `
+ 一行一个域名, 可使用冒号分隔根域名(example.cn.eu.org)与子域名(www), 填写为:www:example.cn.eu.org
+ 支持传递自定义参数
+ `,
+ 'Regular exp.': '匹配正则表达式',
+ 'regHelp': '可使用 @1 指定第一个IPv6地址, @2 指定第二个IPv6地址... 也可使用正则表达式匹配指定的IPv6地址, 留空则不启用',
+ 'Others': '其他',
+ 'Deny from WAN': '禁止公网访问',
+ 'NotAllowWanAccessHelp': '默认启用, 可禁止从公网访问本页面',
+ 'Username': '用户名',
+ 'accountHelp': '为保护你的信息安全,建议输入',
+ 'Password': '密码',
+ 'WebhookURLHelp': `
+ 点击参考官方 Webhook 说明
+
+ 支持的变量 #{ipv4Addr}, #{ipv4Result}, #{ipv4Domains}, #{ipv6Addr}, #{ipv6Result}, #{ipv6Domains}
+ `,
+ 'WebhookRequestBodyHelp': '如果 RequestBody 为空, 则为 GET 请求, 否则为 POST 请求。支持的变量同上',
+ 'WebhookHeadersHelp': '一行一个Header, 如: Authorization: Bearer API_KEY',
+ 'Try it': '模拟测试Webhook',
+ 'Clear': '清空',
+ 'OK': '确定',
+ "Ipv4UrlHelp": "https://myip4.ipip.net, https://ddns.oray.com/checkip, https://ip.3322.net",
+ "Ipv6UrlHelp": "https://speed.neu6.edu.cn/getIP.php, https://v6.ident.me, https://6.ipw.cn",
+ "Ipv4NetInterfaceHelp": "通过网卡获取IPv4",
+ "Ipv6NetInterfaceHelp": "如不指定匹配正则表达式,将默认使用第一个 IPv6 地址",
+ "Ipv4CmdHelp": `
+ 通过命令获取IPv4, 仅使用标准输出(stdout)的第一个匹配的 IPv4 地址。如: ip -4 addr show eth1
+ 点击参考更多
+ `,
+ "Ipv6CmdHelp": `
+ 通过命令获取IPv6, 仅使用标准输出(stdout)的第一个匹配的 IPv6 地址。如: ip -6 addr show eth1
+ 点击参考更多
+ `,
+ "NetInterfaceEmptyHelp": '没有找到可用的网卡 ',
+ }
+};
diff --git a/static/i18n.js b/static/i18n.js
new file mode 100644
index 000000000..786546069
--- /dev/null
+++ b/static/i18n.js
@@ -0,0 +1,49 @@
+const LANG = localStorage.getItem('lang') || (navigator.language || navigator.browserLanguage).replaceAll('_', '-').toLowerCase();
+
+// 支持两种调用方式:
+// 1. 文本的key + (可选:语言映射字典),{en: {hello: "hello", world: "world"}, zh: {hello: "你好", world: "世界"}}
+// 2. 语言字符串字典,{en: "hello", zh: "你好"}
+const i18n = (key, langMap = I18N_MAP) => {
+ if (typeof key !== 'string') {
+ langMap = key;
+ key = null;
+ }
+ // 优先取地区语言,否则取表示语言,再否则取表示语言相同的地区语言,最后取英文
+ let lang = 'en';
+ if (LANG in langMap) {
+ lang = LANG;
+ } else if (LANG.split('-')[0] in langMap) {
+ lang = LANG.split('-')[0];
+ } else {
+ for (const l in langMap) {
+ if (l.split('-')[0] === LANG.split('-')[0]) {
+ lang = l;
+ break;
+ }
+ }
+ }
+ let text = '';
+ if (key) {
+ text = langMap[lang][key];
+ } else {
+ text = langMap[lang];
+ }
+ if (text === undefined) {
+ console.warn(`i18n: No translation for ${key}`);
+ return key;
+ }
+ return text;
+}
+
+const convertDom = (dom = document, ...args) => {
+ dom.querySelectorAll('[data-i18n]').forEach(el => {
+ const key = el.dataset.i18n;
+ el.textContent = i18n(key, ...args);
+ });
+ dom.querySelectorAll('[data-i18n_html]').forEach(el => {
+ const key = el.dataset.i18n_html;
+ el.innerHTML = i18n(key, ...args);
+ });
+}
+
+document.addEventListener('DOMContentLoaded', () => {convertDom();});
\ No newline at end of file
diff --git a/static/utils.js b/static/utils.js
index 3624193f3..ef9311056 100644
--- a/static/utils.js
+++ b/static/utils.js
@@ -1,71 +1,110 @@
-// 常量资源
-const SVG_CODE = {
- success: ` `,
- info: ` `,
- warning: ' ',
- error: ' '
-}
-
-
-
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
+const html2Element = (htmlString) => {
+ const doc = new DOMParser().parseFromString(htmlString, 'text/html')
+ return doc.body.firstElementChild
+}
+
// 在页面顶部显示一行消息,并在若干秒后自动消失
const showMessage = async (msgObj) => {
+ // 填充默认值
+ msgObj = Object.assign({
+ type: 'info',
+ content: '',
+ html: false,
+ duration: 3000
+ }, msgObj)
// 当前是否有消息容器
- let $container = document.querySelector('#msg-container')
+ let $container = document.getElementById('msg-container')
if (!$container) {
// 创建消息容器
- $container = document.createElement('div')
- $container.id = 'msg-container'
+ $container = html2Element('
')
document.body.appendChild($container)
}
// 创建消息元素
- const $msg = document.createElement('div')
+ const $msg = html2Element('
')
// 创建两个span,用于显示消息的图标和内容
- const $icon = document.createElement('span')
- const $content = document.createElement('span')
- $icon.classList.add('msg-icon')
+ const $content = html2Element(' ')
+
+ // 填充内容,根据html属性决定使用text还是html
+ if (msgObj.html) {
+ $content.innerHTML = msgObj.content
+ } else {
+ $content.textContent = msgObj.content
+ }
// 根据消息类型设置图标
- $icon.innerHTML = SVG_CODE[msgObj.type] || SVG_CODE.info
- $content.innerText = msgObj.content || ''
- $msg.appendChild($icon)
+ $msg.innerHTML = `${SVG_CODE[msgObj.type]} `
$msg.appendChild($content)
- // 增加出现动画
- $msg.classList.add('msg','msg-fade')
$container.appendChild($msg)
- // 0延迟是为了让剩余的代码存入异步队列,稍后执行。否则浏览器会把两步操作合并,导致动画不生效
+ // 确保动画生效
await delay(0)
$msg.classList.remove('msg-fade')
// 等待动画结束
await delay(200)
- // 消失函数
- const disappear = async () => {
- // 清除计时器
- clearTimeout(timer)
+ // 销毁函数
+ const destroy = async () => {
// 增加消失动画
$msg.classList.add('msg-fade')
// 动画结束后移除元素
- await delay(200);
- $container.removeChild($msg)
+ await delay(200)
+ $msg.remove()
// 如果容器中没有消息了,移除容器
- if ($container.children.length === 0) {
- document.body.removeChild($container)
+ if (!$container.children.length) {
+ $container.remove()
}
}
// 如果duration为0,则不自动消失
if (msgObj.duration === 0) {
- return disappear
+ return destroy
}
// 自动消失计时器
- let timer = setTimeout(disappear, msgObj.duration || 3000)
+ let timer = setTimeout(destroy, msgObj.duration)
// 注册鼠标事件,鼠标移入时取消自动消失
- $msg.onmouseenter = () => {
+ $msg.addEventListener('mouseenter', () => {
clearTimeout(timer)
- }
+ })
// 鼠标移出时重新计时
- $msg.onmouseleave = () => {
- timer = setTimeout(disappear, msgObj.duration || 3000)
+ $msg.addEventListener('mouseleave', () => {
+ timer = setTimeout(destroy, msgObj.duration)
+ })
+ return destroy
+}
+
+const request = {
+ baseURL: './',
+ parse: async function(resp) {
+ const text = await resp.text()
+ try {
+ return JSON.parse(text)
+ } catch (e) {
+ return text
+ }
+ },
+ stringify: function(dict) {
+ const result = []
+ for (let key in dict) {
+ if (!dict.hasOwnProperty(key)) {
+ continue
+ }
+ // 所有空值将被删除
+ if (String(dict[key])) {
+ result.push(`${key}=${encodeURIComponent(dict[key])}`)
+ }
+ }
+ return result.join('&')
+ },
+ get: async function(path, data, parseFunc) {
+ const response = await fetch(`${this.baseURL}${path}?${this.stringify(data)}`)
+ return await (parseFunc||this.parse)(response)
+ },
+ post: async function(path, data, parseFunc) {
+ if (typeof data === 'object') {
+ data = JSON.stringify(data)
+ }
+ const response = await fetch(`${this.baseURL}${path}`, {
+ method: 'POST',
+ body: data
+ })
+ return await (parseFunc||this.parse)(response)
}
- return disappear
}
\ No newline at end of file
diff --git a/util/messages.go b/util/messages.go
index 7b053e8ea..7187c52da 100644
--- a/util/messages.go
+++ b/util/messages.go
@@ -56,6 +56,7 @@ func init() {
message.SetString(language.English, "启用外网访问, 必须输入登录用户名/密码", "Enable external network access, you must enter the login username/password")
message.SetString(language.English, "修改 '通过命令获取' 必须设置帐号密码,请先设置帐号密码", "Modify 'Get by command' must set username/password, please set username/password first")
message.SetString(language.English, "密码不安全!尝试使用更长的密码", "insecure password, try using a longer password")
+ message.SetString(language.English, "数据解析失败, 请刷新页面重试", "Data parsing failed, please refresh the page and try again")
// config
message.SetString(language.English, "从网卡获得IPv4失败", "Get IPv4 from network card failed")
diff --git a/web/logs.go b/web/logs.go
index 352452e77..edad41fd8 100644
--- a/web/logs.go
+++ b/web/logs.go
@@ -34,12 +34,7 @@ func init() {
// Logs web
func Logs(writer http.ResponseWriter, request *http.Request) {
// mlogs.Logs数组转为json
- logs, err := json.Marshal(mlogs.Logs)
- if err != nil {
- // 返回错误代码
- http.Error(writer, err.Error(), http.StatusInternalServerError)
- return
- }
+ logs, _ := json.Marshal(mlogs.Logs)
writer.Write(logs)
}
diff --git a/web/netInterfaces.go b/web/netInterfaces.go
deleted file mode 100755
index c16ea8c4e..000000000
--- a/web/netInterfaces.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package web
-
-import (
- "encoding/json"
- "net/http"
-
- "github.com/jeessy2/ddns-go/v6/config"
-)
-
-// Ipv4NetInterfaces 获得Ipv4网卡信息
-func Ipv4NetInterfaces(writer http.ResponseWriter, request *http.Request) {
- ipv4, _, err := config.GetNetInterface()
- if len(ipv4) > 0 && err == nil {
- byt, err := json.Marshal(ipv4)
- if err == nil {
- writer.Write(byt)
- return
- }
- }
-}
-
-// Ipv6NetInterfaces 获得Ipv6网卡信息
-func Ipv6NetInterfaces(writer http.ResponseWriter, request *http.Request) {
- _, ipv6, err := config.GetNetInterface()
- if len(ipv6) > 0 && err == nil {
- byt, err := json.Marshal(ipv6)
- if err == nil {
- writer.Write(byt)
- return
- }
- }
-}
diff --git a/web/save.go b/web/save.go
index 1c9597e33..bca8b971e 100755
--- a/web/save.go
+++ b/web/save.go
@@ -28,9 +28,26 @@ func Save(writer http.ResponseWriter, request *http.Request) {
}
func checkAndSave(request *http.Request) string {
- conf, err := config.GetConfigCached()
- usernameNew := strings.TrimSpace(request.FormValue("Username"))
- passwordNew := request.FormValue("Password")
+ conf, _ := config.GetConfigCached()
+
+ // 从请求中读取 JSON 数据
+ var data struct {
+ Username string `json:"Username"`
+ Password string `json:"Password"`
+ NotAllowWanAccess bool `json:"NotAllowWanAccess"`
+ WebhookURL string `json:"WebhookURL"`
+ WebhookRequestBody string `json:"WebhookRequestBody"`
+ WebhookHeaders string `json:"WebhookHeaders"`
+ DnsConf []dnsConf4JS `json:"DnsConf"`
+ }
+
+ // 解析请求中的 JSON 数据
+ err := json.NewDecoder(request.Body).Decode(&data)
+ if err != nil {
+ return util.LogStr("数据解析失败, 请刷新页面重试")
+ }
+ usernameNew := strings.TrimSpace(data.Username)
+ passwordNew := data.Password
// 国际化
accept := request.Header.Get("Accept-Language")
@@ -52,15 +69,14 @@ func checkAndSave(request *http.Request) string {
(usernameNew != "" || passwordNew != "") {
return util.LogStr("若从未设置过帐号密码, 仅允许在ddns-go启动后 5 分钟内设置, 请重启ddns-go")
}
-
}
- conf.NotAllowWanAccess = request.FormValue("NotAllowWanAccess") == "on"
+ conf.NotAllowWanAccess = data.NotAllowWanAccess
conf.Username = usernameNew
conf.Password = passwordNew
- conf.WebhookURL = strings.TrimSpace(request.FormValue("WebhookURL"))
- conf.WebhookRequestBody = strings.TrimSpace(request.FormValue("WebhookRequestBody"))
- conf.WebhookHeaders = strings.TrimSpace(request.FormValue("WebhookHeaders"))
+ conf.WebhookURL = strings.TrimSpace(data.WebhookURL)
+ conf.WebhookRequestBody = strings.TrimSpace(data.WebhookRequestBody)
+ conf.WebhookHeaders = strings.TrimSpace(data.WebhookHeaders)
// 如启用公网访问,帐号密码不能为空
if !conf.NotAllowWanAccess && (conf.Username == "" || conf.Password == "") {
@@ -79,11 +95,7 @@ func checkAndSave(request *http.Request) string {
}
}
- dnsConfFromJS := []dnsConf4JS{}
- err = json.Unmarshal([]byte(request.FormValue("DnsConf")), &dnsConfFromJS)
- if err != nil {
- return "Please refresh the browser and try again"
- }
+ dnsConfFromJS := data.DnsConf
dnsConfArray := []config.DnsConfig{}
empty := dnsConf4JS{}
for k, v := range dnsConfFromJS {
@@ -95,7 +107,7 @@ func checkAndSave(request *http.Request) string {
dnsConf.DNS.Name = v.DnsName
dnsConf.DNS.ID = strings.TrimSpace(v.DnsID)
dnsConf.DNS.Secret = strings.TrimSpace(v.DnsSecret)
- dnsConf.Ipv4.Enable = v.Ipv4Enable == "on"
+ dnsConf.Ipv4.Enable = v.Ipv4Enable
dnsConf.Ipv4.GetType = v.Ipv4GetType
dnsConf.Ipv4.URL = strings.TrimSpace(v.Ipv4Url)
dnsConf.Ipv4.NetInterface = v.Ipv4NetInterface
@@ -105,12 +117,12 @@ func checkAndSave(request *http.Request) string {
} else {
dnsConf.Ipv4.Domains = strings.Split(v.Ipv4Domains, "\n")
}
- dnsConf.Ipv6.Enable = v.Ipv6Enable == "on"
+ dnsConf.Ipv6.Enable = v.Ipv6Enable
dnsConf.Ipv6.GetType = v.Ipv6GetType
dnsConf.Ipv6.URL = strings.TrimSpace(v.Ipv6Url)
dnsConf.Ipv6.NetInterface = v.Ipv6NetInterface
dnsConf.Ipv6.Cmd = strings.TrimSpace(v.Ipv6Cmd)
- dnsConf.Ipv6.IPv6Reg = strings.TrimSpace(v.IPv6Reg)
+ dnsConf.Ipv6.Ipv6Reg = strings.TrimSpace(v.Ipv6Reg)
if strings.Contains(v.Ipv6Domains, "\r\n") {
dnsConf.Ipv6.Domains = strings.Split(v.Ipv6Domains, "\r\n")
} else {
diff --git a/web/webhookTest.go b/web/webhookTest.go
index 936dfb2c2..6d87de8bc 100755
--- a/web/webhookTest.go
+++ b/web/webhookTest.go
@@ -1,19 +1,33 @@
package web
import (
+ "encoding/json"
"net/http"
- "strings"
-
- "github.com/jeessy2/ddns-go/v6/util"
"github.com/jeessy2/ddns-go/v6/config"
+ "github.com/jeessy2/ddns-go/v6/util"
)
-// WebhookTest 测试webhook
func WebhookTest(writer http.ResponseWriter, request *http.Request) {
- url := strings.TrimSpace(request.FormValue("URL"))
- requestBody := strings.TrimSpace(request.FormValue("RequestBody"))
- webhookHeaders := strings.TrimSpace(request.FormValue("WebhookHeaders"))
+ var data struct {
+ URL string `json:"URL"`
+ RequestBody string `json:"RequestBody"`
+ Headers string `json:"Headers"`
+ }
+ err := json.NewDecoder(request.Body).Decode(&data)
+ if err != nil {
+ util.Log("数据解析失败, 请刷新页面重试")
+ return
+ }
+
+ url := data.URL
+ requestBody := data.RequestBody
+ headers := data.Headers
+
+ if url == "" {
+ util.Log("请输入Webhook的URL")
+ return
+ }
var domains = make([]*config.Domain, 1)
domains[0] = &config.Domain{}
@@ -32,13 +46,9 @@ func WebhookTest(writer http.ResponseWriter, request *http.Request) {
Webhook: config.Webhook{
WebhookURL: url,
WebhookRequestBody: requestBody,
- WebhookHeaders: webhookHeaders,
+ WebhookHeaders: headers,
},
}
- if url != "" {
- config.ExecWebhook(fakeDomains, fakeConfig)
- } else {
- util.Log("请输入Webhook的URL")
- }
+ config.ExecWebhook(fakeDomains, fakeConfig)
}
diff --git a/web/writing.go b/web/writing.go
index 78c0afbef..14129c78b 100755
--- a/web/writing.go
+++ b/web/writing.go
@@ -17,32 +17,24 @@ var writingEmbedFile embed.FS
const VersionEnv = "DDNS_GO_VERSION"
-type writingData struct {
- DnsConf template.JS
- NotAllowWanAccess string
- config.User
- config.Webhook
- Version string
-}
-
// js中的dns配置
type dnsConf4JS struct {
DnsName string
DnsID string
DnsSecret string
TTL string
- Ipv4Enable string
+ Ipv4Enable bool
Ipv4GetType string
Ipv4Url string
Ipv4NetInterface string
Ipv4Cmd string
Ipv4Domains string
- Ipv6Enable string
+ Ipv6Enable bool
Ipv6GetType string
Ipv6Url string
Ipv6NetInterface string
Ipv6Cmd string
- IPv6Reg string
+ Ipv6Reg string
Ipv6Domains string
}
@@ -56,19 +48,34 @@ func Writing(writer http.ResponseWriter, request *http.Request) {
}
conf, err := config.GetConfigCached()
-
// 默认禁止公网访问
if err != nil {
conf.NotAllowWanAccess = true
}
- tmpl.Execute(writer, &writingData{
+ ipv4, ipv6, _ := config.GetNetInterface()
+
+ err = tmpl.Execute(writer, struct {
+ DnsConf template.JS
+ NotAllowWanAccess bool
+ config.User
+ config.Webhook
+ Version string
+ Ipv4 []config.NetInterface
+ Ipv6 []config.NetInterface
+ }{
DnsConf: template.JS(getDnsConfStr(conf.DnsConf)),
- NotAllowWanAccess: BooltoOn(conf.NotAllowWanAccess),
+ NotAllowWanAccess: conf.NotAllowWanAccess,
User: conf.User,
Webhook: conf.Webhook,
Version: os.Getenv(VersionEnv),
+ Ipv4: ipv4,
+ Ipv6: ipv6,
})
+ if err != nil {
+ fmt.Println("Error happened..")
+ fmt.Println(err)
+ }
}
func getDnsConfStr(dnsConf []config.DnsConfig) string {
@@ -81,18 +88,18 @@ func getDnsConfStr(dnsConf []config.DnsConfig) string {
DnsID: idHide,
DnsSecret: secretHide,
TTL: conf.TTL,
- Ipv4Enable: BooltoOn(conf.Ipv4.Enable),
+ Ipv4Enable: conf.Ipv4.Enable,
Ipv4GetType: conf.Ipv4.GetType,
Ipv4Url: conf.Ipv4.URL,
Ipv4NetInterface: conf.Ipv4.NetInterface,
Ipv4Cmd: conf.Ipv4.Cmd,
Ipv4Domains: strings.Join(conf.Ipv4.Domains, "\r\n"),
- Ipv6Enable: BooltoOn(conf.Ipv6.Enable),
+ Ipv6Enable: conf.Ipv6.Enable,
Ipv6GetType: conf.Ipv6.GetType,
Ipv6Url: conf.Ipv6.URL,
Ipv6NetInterface: conf.Ipv6.NetInterface,
Ipv6Cmd: conf.Ipv6.Cmd,
- IPv6Reg: conf.Ipv6.IPv6Reg,
+ Ipv6Reg: conf.Ipv6.Ipv6Reg,
Ipv6Domains: strings.Join(conf.Ipv6.Domains, "\r\n"),
})
}
@@ -117,10 +124,3 @@ func getHideIDSecret(conf *config.DnsConfig) (idHide string, secretHide string)
}
return
}
-
-func BooltoOn(b bool) string {
- if b {
- return "on"
- }
- return ""
-}
diff --git a/web/writing.html b/web/writing.html
index c888511a9..fc939d2fe 100755
--- a/web/writing.html
+++ b/web/writing.html
@@ -14,7 +14,9 @@
/>
+
+
@@ -29,15 +31,15 @@
DDNS-GO
Logs
{{.Version}}
@@ -45,12 +47,15 @@
-
+
@@ -143,23 +149,24 @@
DNS Provider
- Auto
- 1s
- 5s
- 10s
- 1m
- 2m
- 10m
- 30m
- 1h
+ Auto
+ 1s
+ 5s
+ 10s
+ 1m
+ 2m
+ 10m
+ 30m
+ 1h
-
- You can modify it if the account supports a smaller TTL.
- The TTL will only be updated when the IP changes
-
+
@@ -170,9 +177,9 @@ IPv4
@@ -249,49 +253,72 @@
IPv4
type="url"
class="form-control form"
name="Ipv4Url"
- id="ipv4_url"
- aria-describedby="ipv4_url_help"
- value="https://myip4.ipip.net, https://ddns.oray.com/checkip, https://ip.3322.net, https://4.ipw.cn"
+ id="Ipv4Url"
+ aria-describedby="Ipv4UrlHelp"
+ data-visible="url"
/>
+ aria-describedby="Ipv4NetInterfaceHelp"
+ data-visible="netInterface"
+ >
+ {{range .Ipv4}}
+
+ {{.Name}}{{.Address}}
+
+ {{end}}
+
+
+
@@ -302,9 +329,9 @@ IPv6
@@ -460,13 +507,16 @@ IPv6
Save
-
-
-
-
+
+
+
- function showConf(idx) {
- const conf = dnsConf[idx] ?? {};
- for (const k in elemValue) {
- elemValue[k].value = conf[k] ?? "";
- }
- for (const k in elemCheck) {
- elemCheck[k].checked = conf[k] === "on";
- }
- for (const k in elemRadio) {
- elemRadio[k][conf[k]]?.click();
+
+
+ // 负数表示倒数第几个
+ if (configIndex < 0) {
+ configIndex += dnsConf.length;
+ } else {
+ configIndex = Math.min(configIndex, dnsConf.length - 1);
+ }
+ $select.value = configIndex;
+ showConf(configIndex);
+ }
-
+ // 新增配置按钮被点击
+ document.getElementById("addBtn").addEventListener('click', e => {
+ e.preventDefault();
+ configIndex = dnsConf.length;
+ dnsConf[configIndex] = {...defaultDnsConf};
+ // 创建新的option
+ const $index = document.getElementById("index");
+ const $option = html2Element(`
+
+ ${configIndex + 1} - ${dnsConf[configIndex].DnsName}
+
+ `);
+ $index.appendChild($option);
+ $index.value = configIndex;
+ showConf(configIndex);
+ });
-
+ });
-
- if (label === "ipv4") {
- document.getElementById("ipv4_url_help").innerText =
- "https://myip4.ipip.net, https://ddns.oray.com/checkip, https://ip.3322.net";
- } else {
- document.getElementById("ipv6_url_help").innerText =
- "https://speed.neu6.edu.cn/getIP.php, https://v6.ident.me, https://6.ipw.cn";
- document.getElementById("IPv6RegDiv").style.display = "none";
+
+
-
-
-
+
+
+
-
- document.getElementById("WebhookURL_help").innerHTML = `
- 点击参考官方 Webhook 说明
-
- 支持的变量 #{ipv4Addr}, #{ipv4Result}, #{ipv4Domains}, #{ipv6Addr}, #{ipv6Result}, #{ipv6Domains}
- `;
- document.getElementById("WebhookRequestBody_help").innerText =
- "如 RequestBody 为空则为 GET 请求,否则为 POST 请求。支持的变量同上";
- document.getElementById("WebhookHeaders_help").innerText =
- "一行一个Header, 如: Authorization: Bearer API_KEY";
- document.getElementById("webhookTestBtn").innerText = "模拟测试Webhook";
+
+
-
+ });
+