├── PDNS_Manager_HL_Overview.png ├── PDNS_Manager_login_window.png ├── PDNS_Manager_records_window.png ├── PDNS_Manager_signup_window.png ├── res ├── css │ ├── webfonts │ │ ├── fa-brands-400.eot │ │ ├── fa-brands-400.ttf │ │ ├── fa-solid-900.eot │ │ ├── fa-solid-900.ttf │ │ ├── fa-solid-900.woff │ │ ├── fa-brands-400.woff │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.eot │ │ ├── fa-regular-400.ttf │ │ ├── fa-regular-400.woff │ │ ├── fa-regular-400.woff2 │ │ └── fa-solid-900.woff2 │ ├── toastr.min.css │ └── awesome │ │ └── all.min.css └── js │ ├── toastr.min.js │ ├── api.js │ └── toastr.js.map ├── api2 ├── credentials-sample.php ├── settings-sample.php ├── auth_helper.php ├── index.php └── pdns_helper.php ├── composer.json ├── LICENSE ├── README.md └── index.html /PDNS_Manager_HL_Overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/PDNS_Manager_HL_Overview.png -------------------------------------------------------------------------------- /PDNS_Manager_login_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/PDNS_Manager_login_window.png -------------------------------------------------------------------------------- /PDNS_Manager_records_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/PDNS_Manager_records_window.png -------------------------------------------------------------------------------- /PDNS_Manager_signup_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/PDNS_Manager_signup_window.png -------------------------------------------------------------------------------- /res/css/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /res/css/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /res/css/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /res/css/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /res/css/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /res/css/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /res/css/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /res/css/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /res/css/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /res/css/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /res/css/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /res/css/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/HEAD/res/css/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /api2/credentials-sample.php: -------------------------------------------------------------------------------- 1 | array('password' => 'some_md5_password_hash', 'secret' => 'some_otp_secret'), 7 | 8 | 9 | 10 | ]); 11 | 12 | 13 | 14 | define('DDNS_TOKENS', 15 | [ 16 | 'some_token' => 'some_token_description', 17 | 18 | 19 | ]); 20 | 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "guzzlehttp/guzzle": "^7.0", 4 | "slim/slim": "4.*", 5 | "slim/psr7": "^1.3", 6 | "nyholm/psr7": "^1.3", 7 | "nyholm/psr7-server": "^1.0", 8 | "guzzlehttp/psr7": "^1.7", 9 | "http-interop/http-factory-guzzle": "^1.0", 10 | "laminas/laminas-diactoros": "^2.4", 11 | "defuse/php-encryption": "^2.2", 12 | "spomky-labs/otphp": "^8.3", 13 | "endroid/qr-code": "^3.9" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ben 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api2/settings-sample.php: -------------------------------------------------------------------------------- 1 | 3x AUTH_INTERVAL (in seconds) 23 | define('AUTH_LIFE', 900); 24 | 25 | 26 | ## Replace with your own by running: php -f ./auth_helper.php 27 | define('AUTH_KEY', 'def0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000936a8ed840e6b8cdcf2c41e89331dabf7b1a13b51bcc93133f5ff5d31a36fe5146720e4390c9e98376e1ade741dc7dd8834d3cb6ea98b7bd8a29c6ef315ea42f'); 28 | 29 | 30 | ## PDNS Api key 31 | define('API_KEY', '00000000000000000000000000000000'); 32 | 33 | 34 | define('ZONE_DEFAULTS', [ 35 | 'dns' => 'dns1.mydns.com.', 36 | 'hostmaster' => 'hostmaster.mydns.com.', 37 | 'nameservers' => ['dns1.mydns.com.', 'dns2.mydns.com.'], 38 | 'masters' => ['dns1.mydns.com.'], 39 | 'refresh' => 14400, 40 | 'retry' => 3600, 41 | 'expire' => 604800, 42 | 'ttl' => 3600, 43 | ]); 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PDNS-Manager 2 | Lightweight PowerDNS management frontend 3 | 4 | 5 | ### Intention and prerequisites 6 | 1. During my IT management responsibilities I found some time to contribute to our team of engineers and company overall. 7 | 2. Tired of looking for a decent PowerDNS frontend manager -> let's create our own. 8 | 3. Should be lightweight, dead simple and could be easily modified if needed. 9 | 4. No need for the 3rd party storage databases (MySQL, PostgreSQL, e.t.c. databases), complex frameworks -> direct communication with the PowerDNS using API 10 | 5. Use PHP, HTML and JavaScript (jQuery) only 11 | 6. Some extra security on top (OTP) 12 | 13 | - - - - 14 | 15 | 16 | ### What works 17 | 1. Adding and removing zones 18 | 2. Modifying records 19 | 3. Adding records (A, TXT, CNAME, MX, SRV) 20 | 4. DDNS minimal usage (check Wiki as well): 21 | * Add: https://pdns.yourdns.com/pdns/ddns/token=aabbcc/name=myhost.example.com[/content=1.2.3.4[/keep=1|0] 22 | * Remove: https://pdns.yourdns.com/pdns/ddns/token=aabbcc/name=myhost.example.com/content=null 23 | 24 | - - - - 25 | 26 | 27 | ### Requirements 28 | 1. PowerDNS 29 | 2. Web server 30 | 3. PHP 31 | 32 | - - - - 33 | 34 | ### Tested on 35 | 1. PHP-FPM: 7.2.4 36 | 2. Ubuntu: 18.04.5 37 | 3. NGINX: 1.14.0 38 | 39 | - - - - 40 | 41 | ### Disclaimer 42 | 1. Use at your own risk 43 | 2. No input data has been validated (apart from adding . (dot) at the end of zones/records), so be careful 44 | 3. If unsure -> add https + basic auth in front of PDNS Manager directory 45 | 4. Feel free to contribute, correct the bugs, add extra functionality 46 | 47 | - - - - 48 | 49 | ### 2DO 50 | - [x] DDNS (via GET) 51 | - [x] DDNS (via POST) 52 | - [x] DDNS get IP from source request by default 53 | - [x] OTP can be turned off by setting secret to 'null' value: e.g. 'user' => array('password' => 'hash', 'secret' => null); and enabled/disabled via settings 54 | - [ ] Protect credentials/token against bruteforcing 55 | - [ ] Validate TXT record to have surrounding quotes 56 | - [ ] Implement TOKEN -> ZONE / RECORD policy 57 | - [ ] Implement USER -> ZONE / RECORD policy 58 | - [ ] Multi-user support (credentials, zones, records) 59 | - [ ] Validate content depending on the record type 60 | - [ ] Pass status codes from PowerDNS API back to application API 61 | - [ ] Cleanup 62 | - [ ] PDNS_Helper -> prepare() -> array 63 | - [ ] Minify everything 64 | - [ ] Possibility to edit settings from the GUI (???) 65 | - [ ] Possibility to edit credentials from the GUI (???) 66 | 67 | - - - - 68 | 69 | ### High level logic overview 70 | ![High level logic overview](https://raw.githubusercontent.com/vbeskrovny/PDNS-Manager/main/PDNS_Manager_HL_Overview.png) 71 | 72 | - - - - 73 | 74 | ### Screenshots 75 | #### Sign in window 76 | ![Sign in window](https://github.com/vbeskrovny/PDNS-Manager/blob/main/PDNS_Manager_login_window.png?raw=true) 77 | 78 | - - - - 79 | 80 | #### Sign up window 81 | ![Sign up window](https://github.com/vbeskrovny/PDNS-Manager/blob/main/PDNS_Manager_signup_window.png?raw=true) 82 | 83 | - - - - 84 | 85 | #### Records editing window 86 | ![Records editing window](https://github.com/vbeskrovny/PDNS-Manager/blob/main/PDNS_Manager_records_window.png?raw=true) 87 | 88 | - - - - 89 | 90 | ### Setup 91 | 1. Clone the project 92 | 2. Configure the web server (NGINX snippet) + enable PHP 93 | ``` 94 | location /pdns/api2 { 95 | root /var/www/pdns.yourdns.com/pdns/api2; 96 | try_files $uri /pdns/api2/index.php$is_args$args; 97 | } 98 | 99 | location / { 100 | try_files $uri $uri/ =404; 101 | } 102 | ``` 103 | 3. Fetch dependencies using composer (check composer.json to see what is needed) 104 | 4. Navigate to https://pdns.yourdns.com -> you should see the login prompt and be able to "sign up" 105 | 5. Save the received credentials to the '-sample.php' files and rename the files (credentials-sample.php and settings-sample.php) by removing '-sample' 106 | 6. Enjoy... 107 | -------------------------------------------------------------------------------- /api2/auth_helper.php: -------------------------------------------------------------------------------- 1 | 0) { 11 | require __DIR__ . '/../vendor/autoload.php'; 12 | printf("\nNew AUTH_KEY: %s\n\n", Key::createNewRandomKey()->saveToAsciiSafeString()); 13 | exit; 14 | } 15 | 16 | 17 | 18 | class AUTH_Helper { 19 | 20 | private $key; 21 | 22 | function __construct() { 23 | $this->key = Key::loadFromAsciiSafeString(AUTH_KEY); 24 | } 25 | 26 | 27 | function test($something = null) { 28 | 29 | return $something; 30 | 31 | } 32 | 33 | 34 | function generate_key() { 35 | $key = Key::createNewRandomKey(); 36 | return $key->saveToAsciiSafeString(); 37 | } 38 | 39 | 40 | function encrypt($text) { 41 | return Crypto::encrypt(sprintf('%s', $text), $this->key, $raw_binary = false); 42 | } 43 | 44 | function decrypt($text) { 45 | return Crypto::decrypt(sprintf('%s', $text), $this->key, $raw_binary = false); 46 | } 47 | 48 | 49 | function check_auth($request) { 50 | 51 | $auth_status = false; 52 | 53 | 54 | 55 | $cookies = new \Slim\Psr7\Cookies($request->getCookieParams()); 56 | $auth_cookie = $cookies->get('pdns_auth_cookie'); 57 | 58 | if ($auth_cookie) { 59 | 60 | $auth_cookie_ary = json_decode($this->decrypt($auth_cookie), true); 61 | 62 | 63 | $ts = $auth_cookie_ary['ts']; 64 | $username = $auth_cookie_ary['username']; 65 | $password = $auth_cookie_ary['password']; 66 | 67 | 68 | ## Check TS 69 | if (($ts + AUTH_LIFE + 1) >= time()) { ## TS is valid 70 | 71 | if (array_key_exists($username, AUTH_HASH) && AUTH_HASH[$username]['password'] == md5($password)) { 72 | 73 | $auth_status = true; 74 | 75 | $auth_cookie = 76 | $this->encrypt( 77 | json_encode([ 78 | 'ts' => time(), 79 | 'username' => $username, 80 | 'password' => $password 81 | ]) 82 | ); 83 | 84 | } else { 85 | 86 | $auth_cookie = null; 87 | 88 | } 89 | 90 | 91 | } else { 92 | 93 | $auth_cookie = null; 94 | 95 | } 96 | 97 | 98 | 99 | } else { 100 | 101 | $auth_cookie = null; 102 | 103 | } 104 | 105 | 106 | 107 | 108 | return [ $auth_status, $auth_cookie, 'auth_status' => $auth_status, 'auth_cookie' => $auth_cookie ]; 109 | 110 | } 111 | 112 | 113 | 114 | function is_totp_ok($username, $otp) { 115 | if (OTP_ENABLED) { 116 | if (array_key_exists('secret', AUTH_HASH[$username])) { 117 | if (AUTH_HASH[$username]['secret'] === null) { 118 | return true; 119 | } else { 120 | if (class_exists('OTPHP\TOTP')) { 121 | $totp = new \OTPHP\TOTP(null, AUTH_HASH[$username]['secret']); 122 | return $totp->verify($otp); 123 | } else { 124 | return false; 125 | } 126 | } 127 | } else { 128 | return true; 129 | } 130 | } else { 131 | return true; 132 | } 133 | } 134 | 135 | 136 | function signup($username) { 137 | 138 | if (OTP_ENABLED === true && class_exists('OTPHP\TOTP')) { 139 | 140 | $totp = new \OTPHP\TOTP(); 141 | 142 | $otp_secret = $totp->getSecret(); 143 | $totp = new \OTPHP\TOTP($username, $otp_secret); 144 | $otp_url = $totp->getProvisioningUri(); 145 | 146 | 147 | $qrCode = new QrCode($otp_url); 148 | $otp_qr = $qrCode->writeDataUri(); 149 | 150 | 151 | return [ $otp_secret, $otp_url, $otp_qr ]; 152 | 153 | } else { 154 | 155 | return [ 'TOTP disabled and/or not installed', 'TOTP disabled and/or not installed', null ]; 156 | 157 | } 158 | 159 | } 160 | 161 | 162 | 163 | function do_auth($username, $password, $otp) { 164 | 165 | $auth_status = false; 166 | $auth_cookie = null; 167 | 168 | if (array_key_exists($username, AUTH_HASH) && AUTH_HASH[$username]['password'] == md5($password) && $this->is_totp_ok($username, $otp) === true) { 169 | 170 | $auth_status = true; 171 | 172 | $auth_cookie = 173 | $this->encrypt( 174 | json_encode([ 175 | 'ts' => time(), 176 | 'username' => $username, 177 | 'password' => $password 178 | ]) 179 | ); 180 | 181 | } 182 | 183 | 184 | return [ $auth_status, $auth_cookie ]; 185 | 186 | } 187 | 188 | 189 | } 190 | 191 | 192 | -------------------------------------------------------------------------------- /res/js/toastr.min.js: -------------------------------------------------------------------------------- 1 | !function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("
").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e("
"),M=e("
"),B=e("
"),q=e("
"),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.4",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)}); 2 | //# sourceMappingURL=toastr.js.map 3 | -------------------------------------------------------------------------------- /res/css/toastr.min.css: -------------------------------------------------------------------------------- 1 | .toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#FFF}.toast-message a:hover{color:#CCC;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#FFF;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);line-height:1}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}.rtl .toast-close-button{left:-.3em;float:left;right:.3em}button.toast-close-button{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999;pointer-events:none}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#FFF;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51A351}.toast-error{background-color:#BD362F}.toast-info{background-color:#2F96B4}.toast-warning{background-color:#F89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}} -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PDNS Manager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 79 | 80 | 81 | 82 | 83 | 84 |
85 |
86 |

Please sign in

87 | 88 | 89 | 90 | 91 |
92 | 93 | 94 |
95 | 96 | 97 | 120 | 121 | 122 |
123 |
124 | 125 | 126 | 127 | 128 |
129 | 130 |
131 | 132 |
133 | 134 |
135 |
136 | 137 |
138 |
139 |
140 |
141 | 142 | 143 | 144 | 145 |
146 |
147 | 148 | 149 |
150 | 151 | 152 |
153 | 154 | 155 |
156 | 157 |
158 | 159 | 160 | 161 | 162 | 163 | 164 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /api2/index.php: -------------------------------------------------------------------------------- 1 | setBasePath(URL_PREFIX); 23 | 24 | 25 | $app->get('/test[/{something}]', function (Request $request, Response $response, $args) { 26 | 27 | global $PDNS, $AUTH; 28 | 29 | 30 | // $payload = json_encode([$AUTH->test($args['something'])]); 31 | $payload = json_encode($AUTH->check_auth($request)['auth_status']); 32 | // $payload = json_encode($AUTH->generate_key()); 33 | 34 | 35 | $response->getBody()->write($payload); 36 | return $response->withHeader('Content-Type', 'application/json'); 37 | 38 | }); 39 | 40 | 41 | 42 | 43 | 44 | $app->post('/signup', function (Request $request, Response $response) { 45 | 46 | global $AUTH; 47 | 48 | $params = $request->getParsedBody(); 49 | 50 | 51 | $password_hash = md5($params['password']); 52 | list($otp_secret, $otp_url, $otp_qr) = $AUTH->signup($params['username']); 53 | 54 | 55 | $payload = json_encode(['password_hash' => $password_hash, 'otp_secret' => $otp_secret, 'otp_url' => $otp_url, 'otp_qr' => $otp_qr ]); 56 | 57 | 58 | $response->getBody()->write($payload); 59 | return $response->withHeader('Content-Type', 'application/json'); 60 | 61 | 62 | }); 63 | 64 | 65 | 66 | 67 | $app->get('/check_auth', function (Request $request, Response $response, $args) { 68 | 69 | global $AUTH; 70 | 71 | 72 | list($auth_status, $auth_cookie) = $AUTH->check_auth($request); 73 | 74 | 75 | $payload = json_encode(['auth_status' => $auth_status, 'auth_cookie' => $auth_cookie]); 76 | 77 | 78 | 79 | 80 | $response->getBody()->write($payload); 81 | return $response->withHeader('Content-Type', 'application/json'); 82 | 83 | 84 | }); 85 | 86 | 87 | 88 | $app->post('/do_auth', function (Request $request, Response $response) { 89 | 90 | global $AUTH; 91 | 92 | $params = $request->getParsedBody(); 93 | 94 | $auth_status = false; 95 | $auth_cookie = null; 96 | 97 | if (array_key_exists('username', $params) && array_key_exists('password', $params) && array_key_exists('otp', $params)) { 98 | 99 | list($auth_status, $auth_cookie) = $AUTH->do_auth($params['username'], $params['password'], $params['otp']); 100 | 101 | } 102 | 103 | 104 | 105 | $payload = json_encode(['auth_status' => $auth_status, 'auth_cookie' => $auth_cookie]); 106 | 107 | 108 | $response->getBody()->write($payload); 109 | return $response->withHeader('Content-Type', 'application/json'); 110 | 111 | 112 | }); 113 | 114 | 115 | 116 | ## DDNS via POST 117 | $app->post('/ddns', function (Request $request, Response $response) { 118 | 119 | global $PDNS, $AUTH; 120 | 121 | $params = $request->getParsedBody(); 122 | 123 | 124 | $status = $PDNS->do_ddns($params); 125 | 126 | 127 | $response->getBody()->write($status); 128 | return $response->withHeader('Content-Type', 'text/plain'); 129 | 130 | }); 131 | 132 | 133 | ## DDNS via GET 134 | $app->get('/ddns[/{get_params:.*}]', function (Request $request, Response $response, $args) { 135 | 136 | global $PDNS, $AUTH; 137 | 138 | $params = array(); 139 | $get_params = explode('/', $args['get_params']); 140 | 141 | 142 | foreach ($get_params as $kv_pair) { 143 | if (preg_match('/^(.+)=(.+)$/', $kv_pair, $kv_ary)) { 144 | $key = $kv_ary[1]; 145 | $val = $kv_ary[2]; 146 | $params[$key] = $val; 147 | } 148 | } 149 | 150 | 151 | $status = $PDNS->do_ddns($params); 152 | 153 | $response->getBody()->write($status); 154 | return $response->withHeader('Content-Type', 'text/plain'); 155 | 156 | 157 | }); 158 | 159 | 160 | 161 | $app->post('/globals_init', function (Request $request, Response $response) { 162 | 163 | global $PDNS, $AUTH; 164 | 165 | $settings = array(); 166 | 167 | 168 | if ($AUTH->check_auth($request)['auth_status'] === true) { 169 | 170 | $settings['AUTH_INTERVAL'] = AUTH_INTERVAL; 171 | $settings['ZONE_DEFAULTS'] = ZONE_DEFAULTS; 172 | 173 | } 174 | 175 | 176 | $payload = json_encode($settings); 177 | 178 | 179 | $response->getBody()->write($payload); 180 | return $response->withHeader('Content-Type', 'application/json'); 181 | 182 | 183 | }); 184 | 185 | 186 | 187 | $app->post('/remove_record', function (Request $request, Response $response) { 188 | 189 | global $PDNS, $AUTH; 190 | 191 | if ($AUTH->check_auth($request)['auth_status'] === true) { 192 | 193 | $params = $request->getParsedBody(); 194 | 195 | $PDNS->remove_record($params['zone'], $params['type'], $params['name']); 196 | 197 | 198 | $payload = json_encode(['default' => true]); 199 | 200 | } else { 201 | 202 | $payload = json_encode(['auth_status' => false]); 203 | 204 | } 205 | 206 | 207 | $response->getBody()->write($payload); 208 | return $response->withHeader('Content-Type', 'application/json'); 209 | 210 | 211 | }); 212 | 213 | 214 | 215 | $app->post('/save_records', function (Request $request, Response $response) { 216 | 217 | global $PDNS, $AUTH; 218 | 219 | if ($AUTH->check_auth($request)['auth_status'] === true) { 220 | 221 | $params = $request->getParsedBody(); 222 | 223 | $PDNS->save_records($params); 224 | 225 | 226 | $payload = json_encode(['default' => true]); 227 | 228 | } else { 229 | 230 | $payload = json_encode(['auth_status' => false]); 231 | 232 | } 233 | 234 | 235 | $response->getBody()->write($payload); 236 | return $response->withHeader('Content-Type', 'application/json'); 237 | 238 | 239 | }); 240 | 241 | 242 | 243 | $app->post('/remove_zone/{zone}', function (Request $request, Response $response, $args) { 244 | 245 | global $PDNS, $AUTH; 246 | 247 | 248 | if ($AUTH->check_auth($request)['auth_status'] === true) { 249 | 250 | $PDNS->remove_zone($args['zone']); 251 | 252 | 253 | $payload = json_encode(['default' => true]); 254 | 255 | } else { 256 | 257 | $payload = json_encode(['auth_status' => false]); 258 | 259 | } 260 | 261 | 262 | $response->getBody()->write($payload); 263 | return $response->withHeader('Content-Type', 'application/json'); 264 | 265 | }); 266 | 267 | 268 | 269 | 270 | $app->post('/add_zone', function (Request $request, Response $response) { 271 | 272 | global $PDNS, $AUTH; 273 | 274 | 275 | if ($AUTH->check_auth($request)['auth_status'] === true) { 276 | 277 | $params = $request->getParsedBody(); 278 | 279 | $PDNS->add_zone($params); 280 | 281 | $payload = json_encode(['default' => true]); 282 | 283 | } else { 284 | 285 | $payload = json_encode(['auth_status' => false]); 286 | 287 | } 288 | 289 | 290 | $response->getBody()->write($payload); 291 | return $response->withHeader('Content-Type', 'application/json'); 292 | 293 | 294 | }); 295 | 296 | 297 | $app->post('/get_zones', function (Request $request, Response $response, $args) { 298 | 299 | global $PDNS, $AUTH; 300 | 301 | 302 | if ($AUTH->check_auth($request)['auth_status'] === true) { 303 | 304 | $payload = $PDNS->get_zones(); 305 | 306 | } else { 307 | 308 | $payload = json_encode(['auth_status' => false]); 309 | 310 | } 311 | 312 | 313 | $response->getBody()->write($payload); 314 | return $response->withHeader('Content-Type', 'application/json'); 315 | 316 | }); 317 | 318 | 319 | 320 | $app->post('/get_records/{zone}', function (Request $request, Response $response, $args) { 321 | 322 | global $PDNS, $AUTH; 323 | 324 | if ($AUTH->check_auth($request)['auth_status'] === true) { 325 | 326 | $payload = $PDNS->get_records($args['zone']); 327 | 328 | } else { 329 | 330 | $payload = json_encode(['auth_status' => false]); 331 | 332 | } 333 | 334 | 335 | $response->getBody()->write($payload); 336 | return $response->withHeader('Content-Type', 'application/json'); 337 | 338 | }); 339 | 340 | 341 | 342 | // DEFAULT - catch all 343 | $app->get('/{path:.*}', function ($request, $response, array $args) { 344 | $payload = json_encode(['default' => true]); 345 | $response->getBody()->write($payload); 346 | return $response->withHeader('Content-Type', 'application/json'); 347 | }); 348 | 349 | 350 | 351 | 352 | $app->run(); -------------------------------------------------------------------------------- /res/js/api.js: -------------------------------------------------------------------------------- 1 | 2 | var GLOBALS = {}; 3 | var row_id = 1; 4 | 5 | 6 | function globals_init() { 7 | 8 | $.post('api2/globals_init', function(data) { 9 | GLOBALS = data; 10 | }); 11 | 12 | } 13 | 14 | 15 | function signup() { 16 | 17 | 18 | var form_data = $('#auth_form').serializeArray(); 19 | var all_valid = true; 20 | 21 | for (let key in form_data) { 22 | 23 | if (form_data[key].name == 'username') { 24 | if (form_data[key].value == '') { 25 | all_valid = false; 26 | $('.auth-form-username').toggleClass('is-invalid', true); 27 | } else { 28 | $('.auth-form-username').removeClass('is-invalid'); 29 | } 30 | } else if (form_data[key].name == 'password') { 31 | if (form_data[key].value == '') { 32 | all_valid = false; 33 | $('.auth-form-password').toggleClass('is-invalid', true); 34 | } else { 35 | $('.auth-form-password').removeClass('is-invalid'); 36 | } 37 | } 38 | 39 | 40 | } 41 | 42 | 43 | $('.auth-form-otp').removeClass('is-invalid'); 44 | 45 | 46 | if (all_valid) { 47 | 48 | $.post('api2/signup', form_data, function(data) { 49 | 50 | $('input#passwordHash').val(data.password_hash); 51 | $('input#otpSecret').val(data.otp_secret); 52 | $('input#otpURL').val(data.otp_url); 53 | $('img#otpIMG').attr('src', data.otp_qr); 54 | 55 | 56 | $('.signup-div').show(); 57 | 58 | }); 59 | 60 | } 61 | 62 | } 63 | 64 | 65 | 66 | function update_cookie(content) { 67 | Cookies.set('pdns_auth_cookie', content, { secure: true, expires: 365 }); 68 | } 69 | 70 | 71 | function remove_cookie() { 72 | Cookies.remove('pdns_auth_cookie', { secure: true }); 73 | } 74 | 75 | 76 | 77 | function sign_out() { 78 | remove_cookie(); 79 | location.reload(); 80 | } 81 | 82 | 83 | 84 | function auth_loop() { 85 | 86 | var delay = GLOBALS['AUTH_INTERVAL'] * 1000; 87 | 88 | setTimeout( function() { 89 | 90 | $.get('api2/check_auth', function(data) { 91 | 92 | if (data.auth_status) { 93 | 94 | update_cookie(data.auth_cookie); 95 | 96 | } else { 97 | 98 | sign_out(); 99 | 100 | } 101 | 102 | auth_loop(); 103 | 104 | }); 105 | 106 | 107 | 108 | }, delay); 109 | 110 | 111 | } 112 | 113 | 114 | 115 | function do_auth() { 116 | 117 | var form_data = $('#auth_form').serializeArray(); 118 | 119 | $.post('api2/do_auth', form_data, function(data) { 120 | 121 | if (data.auth_status) { 122 | 123 | update_cookie(data.auth_cookie); 124 | location.reload(); 125 | 126 | } else { 127 | 128 | remove_cookie(); 129 | 130 | $('.auth-form-username').toggleClass('is-invalid', true); 131 | $('.auth-form-password').toggleClass('is-invalid', true); 132 | $('.auth-form-otp').toggleClass('is-invalid', true); 133 | 134 | } 135 | 136 | 137 | 138 | }); 139 | 140 | 141 | 142 | return false; 143 | 144 | } 145 | 146 | 147 | 148 | function save_records() { 149 | 150 | var zone = $('#zones :selected').val(); 151 | if (zone && zone != 0) { 152 | 153 | var form_data = $('#records_form').serializeArray(); 154 | 155 | var all_valid = true; 156 | 157 | $('#records_form .record-data').each(function( index ) { 158 | 159 | if (this.value == '') { 160 | all_valid = false; 161 | $(this).toggleClass('is-invalid', true); 162 | } else { 163 | $(this).removeClass('is-invalid'); 164 | } 165 | 166 | }); 167 | 168 | 169 | if (all_valid) { 170 | 171 | $.post('api2/save_records', form_data, function(data) { 172 | get_records(zone); 173 | toastr.success('Records has been saved!'); 174 | }); 175 | 176 | } 177 | 178 | 179 | } 180 | 181 | } 182 | 183 | 184 | function remove_record(zone, type, name) { 185 | 186 | if (confirm('Proceed with the "' + type + '" type for "' + name + '" record removal?')) { 187 | $.post('api2/remove_record', { zone: zone, type: type, name: name }, function(data) { 188 | get_records(zone); 189 | }); 190 | } 191 | 192 | } 193 | 194 | 195 | 196 | function remove_row(row_id) { 197 | $('#row_' + row_id).remove(); 198 | } 199 | 200 | 201 | function remove_zone() { 202 | var zone = $('#zones :selected').val(); 203 | if (zone && zone != 0) { 204 | if (confirm('Proceed with the "' + zone + '" zone removal?')) { 205 | $.post('api2/remove_zone/' + zone, function(data) { 206 | clear_records(); 207 | refresh_zones(); 208 | }); 209 | } 210 | } 211 | } 212 | 213 | 214 | 215 | function refresh_records() { 216 | var zone = $('#zones :selected').val(); 217 | if (zone && zone != 0) { 218 | get_records(zone); 219 | } 220 | refresh_zones(zone); 221 | 222 | toastr.info('Records has been re-loaded...'); 223 | 224 | } 225 | 226 | 227 | 228 | function clear_records() { 229 | $('#records').empty(); 230 | } 231 | 232 | 233 | 234 | function add_zone(params) { 235 | $.post('api2/add_zone', params, function (data) { 236 | clear_records(); 237 | refresh_zones(); 238 | toastr.success('The new zone [' + params['zone'] + '] has been created!'); 239 | }); 240 | } 241 | 242 | 243 | function get_records(zone) { 244 | 245 | $.post('api2/get_records/' + zone, function(data) { 246 | 247 | 248 | clear_records(); 249 | 250 | 251 | for (let key in data) { 252 | 253 | // console.log(data[key]); 254 | 255 | var type = data[key].type; 256 | var name = data[key].name; 257 | var records = data[key].records; 258 | var ttl = data[key].ttl; 259 | 260 | // console.log(records); 261 | 262 | 263 | records.forEach(function(value, index, array) { 264 | 265 | var content = value.content; 266 | 267 | var record_tpl = `
268 |
269 |
270 |
271 |
272 |
273 |
`; 274 | 275 | $('#records').append(record_tpl); 276 | 277 | row_id++; 278 | 279 | }); 280 | 281 | 282 | 283 | 284 | } 285 | 286 | 287 | var add_record_tpl = `
288 |
289 | 290 |
291 |
`; 292 | 293 | $('#records').append(add_record_tpl); 294 | 295 | 296 | }); 297 | 298 | 299 | } 300 | 301 | function set_type(row_id, type, zone) { 302 | 303 | var type_val = ''; 304 | var name_attr = zone; 305 | var content_attr = ''; 306 | 307 | 308 | if (type != '') { 309 | 310 | type_val = type; 311 | 312 | if (type == 'TXT') { 313 | content_attr = '"some text value surrounded by quotes"'; 314 | } else if (type == 'A') { 315 | content_attr = '127.1.2.3'; 316 | } else if (type == 'CNAME') { 317 | content_attr = 'domain01.' + zone; 318 | } else if (type == 'MX') { 319 | content_attr = '10 ' + zone; 320 | } else if (type == 'SRV') { 321 | name_attr = '_sip._udp.sip.' + zone; 322 | content_attr = 'prio[10] weight[1] port[5060] sip.' + zone; 323 | } 324 | 325 | } 326 | 327 | 328 | $('#type_field_' + row_id).val(type_val); 329 | $('#name_field_' + row_id).attr('placeholder', name_attr); 330 | $('#content_field_' + row_id).attr('placeholder', content_attr); 331 | 332 | 333 | } 334 | 335 | 336 | 337 | function add_record(zone) { 338 | 339 | var dh = $(document).height(); 340 | 341 | 342 | var new_record_tpl = `
343 |
344 | 352 | 353 |
354 |
355 |
356 |
357 |
358 |
`; 359 | 360 | 361 | $(new_record_tpl).insertBefore( $('#add_record_btn_div') ); 362 | row_id++; 363 | 364 | 365 | 366 | var diff = $(document).height() - dh; 367 | window.scrollBy(0, diff); 368 | 369 | 370 | 371 | } 372 | 373 | 374 | 375 | function refresh_zones(default_zone = null) { 376 | 377 | $.post('api2/get_zones', function(data) { 378 | 379 | var list = $('#zones').empty(); 380 | list.append($('