wild card UI done
This commit is contained in:
+38
-35
@@ -30,6 +30,7 @@ class Host extends Table{
|
|||||||
'is_wildcard': {default: false, isRequired: false, type: 'boolean',},
|
'is_wildcard': {default: false, isRequired: false, type: 'boolean',},
|
||||||
'wildcard_status': {isRequired: false, type: 'string', min: 3, max: 500, default: 'Requesting'},
|
'wildcard_status': {isRequired: false, type: 'string', min: 3, max: 500, default: 'Requesting'},
|
||||||
'wildcard_parent': {isRequired: false, type: 'string', min: 3, max: 500},
|
'wildcard_parent': {isRequired: false, type: 'string', min: 3, max: 500},
|
||||||
|
'wildcard_expires': {isRequired: false, type: 'number'},
|
||||||
}
|
}
|
||||||
|
|
||||||
static lookUpObj = {};
|
static lookUpObj = {};
|
||||||
@@ -62,7 +63,7 @@ class Host extends Table{
|
|||||||
parent: parentOBJ.host
|
parent: parentOBJ.host
|
||||||
});
|
});
|
||||||
}catch(error){
|
}catch(error){
|
||||||
console.error('add cahce error', {...parentOBJ, host, is_cache: true}, error)
|
console.error('add cache error', {...parentOBJ, host, is_cache: true}, error)
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,16 +104,18 @@ class Host extends Table{
|
|||||||
|
|
||||||
try{
|
try{
|
||||||
let host = this;
|
let host = this;
|
||||||
|
|
||||||
|
await host.update({
|
||||||
|
wildcard_status: 'Requesting',
|
||||||
|
});
|
||||||
let cert = await letsEncrypt.dnsWildcard(this.host, {
|
let cert = await letsEncrypt.dnsWildcard(this.host, {
|
||||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
|
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
|
||||||
host.update({
|
await host.update({
|
||||||
wildcard_status: `Adding record for ${authz.identifier.value}`
|
wildcard_status: `Adding record`
|
||||||
});
|
});
|
||||||
try{
|
try{
|
||||||
let parts = tldExtract(authz.identifier.value);
|
let parts = tldExtract(authz.identifier.value);
|
||||||
|
|
||||||
console.log('adding DNS record for', `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`)
|
|
||||||
|
|
||||||
let res = await porkBun.createRecordForce(
|
let res = await porkBun.createRecordForce(
|
||||||
parts.domain,
|
parts.domain,
|
||||||
{
|
{
|
||||||
@@ -121,56 +124,55 @@ class Host extends Table{
|
|||||||
content: `${keyAuthorization}`
|
content: `${keyAuthorization}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
console.log('porkbun res', res)
|
|
||||||
}catch(error){
|
}catch(error){
|
||||||
console.log('model Host challengeCreateFn error:', error)
|
console.log('model Host challengeCreateFn error:', error)
|
||||||
host.update({
|
await host.update({
|
||||||
wildcard_status: `Add DNS record failed`
|
wildcard_status: `Add DNS record failed`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDnsCheck: async(authz, checkCount)=>{
|
onDnsCheck: async(authz, checkCount)=>{
|
||||||
host.update({
|
await host.update({
|
||||||
wildcard_status: `${checkCount} Checking DNS for ${authz.identifier.value}`
|
wildcard_status: `${checkCount} Checking DNS`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDnsCheckFail: async(authz, error)=>{
|
onDnsCheckFail: async(authz, error)=>{
|
||||||
host.update({
|
await host.update({
|
||||||
wildcard_status: `DNS check failed for ${authz.identifier.value}`
|
wildcard_status: `DNS check failed for ${authz.identifier.value}`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDnsCheckFound: async(authz)=>{
|
onDnsCheckFound: async(authz)=>{
|
||||||
host.update({
|
await host.update({
|
||||||
wildcard_status: `DNS check found for ${authz.identifier.value}`
|
wildcard_status: `DNS check found`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDnsCheckSuccess: async(authz)=>{
|
onDnsCheckSuccess: async(authz)=>{
|
||||||
host.update({
|
await host.update({
|
||||||
wildcard_status: `DNS check success for ${authz.identifier.value}`
|
wildcard_status: `DNS check success`
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onDnsCheckRemove: async(authz)=>{
|
onDnsCheckRemove: async(authz)=>{
|
||||||
host.update({
|
await host.update({
|
||||||
wildcard_status: `DNS remove record for ${authz.identifier.value}`
|
wildcard_status: `DNS remove record`
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
challengeRemoveFn: async (authz, challenge, keyAuthorization)=>{
|
challengeRemoveFn: async (authz, challenge, keyAuthorization)=>{
|
||||||
host.update({
|
await host.update({
|
||||||
wildcard_status: `DNS remove record for ${authz.identifier.value}`
|
wildcard_status: `DNS remove record`
|
||||||
})
|
})
|
||||||
try{
|
try{
|
||||||
// let parts = tldExtract(authz.identifier.value);
|
let parts = tldExtract(authz.identifier.value);
|
||||||
// await porkBun.deleteRecords(
|
await porkBun.deleteRecords(
|
||||||
// parts.domain,
|
parts.domain,
|
||||||
// {
|
{
|
||||||
// type:'TXT',
|
type:'TXT',
|
||||||
// name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''
|
name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''
|
||||||
// }`,
|
}`,
|
||||||
// content: `${keyAuthorization}`}
|
content: `${keyAuthorization}`}
|
||||||
// );
|
);
|
||||||
|
|
||||||
}catch(error){
|
}catch(error){
|
||||||
host.update({
|
await host.update({
|
||||||
wildcard_status: `DNS remove record failed for ${authz.identifier.value}`
|
wildcard_status: `DNS remove record failed for ${authz.identifier.value}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -188,8 +190,9 @@ class Host extends Table{
|
|||||||
|
|
||||||
|
|
||||||
await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd));
|
await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd));
|
||||||
this.update({
|
await this.update({
|
||||||
wildcard_status: `Done`
|
wildcard_status: `Done`,
|
||||||
|
wildcard_expires: toAdd.real_expiry*1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
@@ -204,8 +207,8 @@ class Host extends Table{
|
|||||||
async update(...args){
|
async update(...args){
|
||||||
try{
|
try{
|
||||||
let out = await super.update(...args)
|
let out = await super.update(...args)
|
||||||
await this.bustCache(this.host)
|
await this.bustCache(this.host);
|
||||||
await Host.buildLookUpObj()
|
await Host.buildLookUpObj();
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
} catch(error){
|
} catch(error){
|
||||||
@@ -215,9 +218,9 @@ class Host extends Table{
|
|||||||
|
|
||||||
async remove(...args){
|
async remove(...args){
|
||||||
try{
|
try{
|
||||||
let out = await super.remove(...args)
|
let out = await super.remove(...args);
|
||||||
await Host.buildLookUpObj()
|
await Host.buildLookUpObj();
|
||||||
await this.bustCache(this.host)
|
await this.bustCache(this.host);
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
} catch(error){
|
} catch(error){
|
||||||
|
|||||||
@@ -26,10 +26,17 @@ app.host = (function(app){
|
|||||||
function remove(args, callack){
|
function remove(args, callack){
|
||||||
app.api.delete('host/'+ args.host, function(error, data){
|
app.api.delete('host/'+ args.host, function(error, data){
|
||||||
callack(error, data);
|
callack(error, data);
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCert(args, callack){
|
||||||
|
app.api.get('cert/'+args.host, function(error, data){
|
||||||
|
callack(error, data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
getCert,
|
||||||
list: list,
|
list: list,
|
||||||
get: get,
|
get: get,
|
||||||
add: add,
|
add: add,
|
||||||
|
|||||||
@@ -311,7 +311,23 @@ app.util = (function(app){
|
|||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function downloadFile(filename, text){
|
||||||
|
// https://stackoverflow.com/a/18197341
|
||||||
|
|
||||||
|
var element = document.createElement('a');
|
||||||
|
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||||||
|
element.setAttribute('download', filename);
|
||||||
|
|
||||||
|
element.style.display = 'none';
|
||||||
|
document.body.appendChild(element);
|
||||||
|
|
||||||
|
element.click();
|
||||||
|
|
||||||
|
document.body.removeChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
downloadFile: downloadFile,
|
||||||
getUrlParameter: getUrlParameter,
|
getUrlParameter: getUrlParameter,
|
||||||
actionMessage: actionMessage
|
actionMessage: actionMessage
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-9
@@ -29,7 +29,19 @@ router.post('/', async function(req, res, next){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/:item(*)', async function(req, res, next){
|
router.get('/lookup/:item', async function(req, res, next){
|
||||||
|
try{
|
||||||
|
return res.json({
|
||||||
|
string: req.params.item,
|
||||||
|
results: await Model.lookUp(req.params.item),
|
||||||
|
});
|
||||||
|
|
||||||
|
}catch(error){
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/:item', async function(req, res, next){
|
||||||
try{
|
try{
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
@@ -41,7 +53,7 @@ router.get('/:item(*)', async function(req, res, next){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.put('/:item(*)', async function(req, res, next){
|
router.put('/:item', async function(req, res, next){
|
||||||
try{
|
try{
|
||||||
req.body.updated_by = req.user.username;
|
req.body.updated_by = req.user.username;
|
||||||
let item = await Model.get(req.params.item);
|
let item = await Model.get(req.params.item);
|
||||||
@@ -59,7 +71,7 @@ router.put('/:item(*)', async function(req, res, next){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.delete('/:item(*)', async function(req, res, next){
|
router.delete('/:item', async function(req, res, next){
|
||||||
try{
|
try{
|
||||||
let item = await Model.get(req.params.item);
|
let item = await Model.get(req.params.item);
|
||||||
let count = await item.remove();
|
let count = await item.remove();
|
||||||
@@ -74,15 +86,16 @@ router.delete('/:item(*)', async function(req, res, next){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/lookup/:item(*)', async function(req, res, next){
|
router.put('/:item/renew', async function(req, res, next){
|
||||||
try{
|
try{
|
||||||
return res.json({
|
let item = await Model.get(req.params.item);
|
||||||
string: req.params.item,
|
item.createWildcardCert();
|
||||||
results: await Model.lookUp(req.params.item),
|
|
||||||
});
|
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
message: `Requesting wildcard cert for ${req.params.item}`,
|
||||||
|
})
|
||||||
}catch(error){
|
}catch(error){
|
||||||
return next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ const sleep = require('./sleep');
|
|||||||
|
|
||||||
// https://dns.google/resolve?name=${name}&type=TXT
|
// https://dns.google/resolve?name=${name}&type=TXT
|
||||||
|
|
||||||
AcmeClient.setLogger((message) => {
|
|
||||||
console.log('ACME:', message);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
class LetsEncrypt{
|
class LetsEncrypt{
|
||||||
static AcmeClient = AcmeClient;
|
static AcmeClient = AcmeClient;
|
||||||
@@ -64,15 +60,12 @@ class LetsEncrypt{
|
|||||||
skipChallengeVerification: true,
|
skipChallengeVerification: true,
|
||||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
|
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
|
||||||
try{
|
try{
|
||||||
console.log('challenge', challenge)
|
dnsToAdd++;
|
||||||
console.log(`start TXT record key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization} challenge=${challenge} googleDNS=https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`)
|
|
||||||
dnsToAdd++
|
|
||||||
let resCheck = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`);
|
let resCheck = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`);
|
||||||
if(resCheck.data.Answer && resCheck.data.Answer.some(record => record.data === keyAuthorization)){
|
if(resCheck.data.Answer && resCheck.data.Answer.some(record => record.data === keyAuthorization)){
|
||||||
await sleep(1000);
|
dnsFound++;
|
||||||
dnsFound++
|
|
||||||
if(dnsFound === dnsToAdd){
|
if(dnsFound === dnsToAdd){
|
||||||
options.onDnsCheckFound(authz, dnsFound)
|
options.onDnsCheckFound(authz, dnsFound);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -83,13 +76,11 @@ class LetsEncrypt{
|
|||||||
while(true){
|
while(true){
|
||||||
options.onDnsCheck(authz, checkCount);
|
options.onDnsCheck(authz, checkCount);
|
||||||
let res = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`);
|
let res = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`);
|
||||||
// console.log(keyAuthorization, res.data);
|
|
||||||
if(res.data.Answer && res.data.Answer.some(record => record.data === keyAuthorization)){
|
if(res.data.Answer && res.data.Answer.some(record => record.data === keyAuthorization)){
|
||||||
dnsFound++
|
dnsFound++;
|
||||||
if(dnsFound === dnsToAdd){
|
if(dnsFound === dnsToAdd){
|
||||||
options.onDnsCheckFound(authz, dnsFound)
|
options.onDnsCheckFound(authz, dnsFound);
|
||||||
}
|
}
|
||||||
// console.log(`found record for key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`)
|
|
||||||
await sleep(10000);
|
await sleep(10000);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-10
@@ -43,9 +43,21 @@
|
|||||||
|
|
||||||
function parseHostRow(host) {
|
function parseHostRow(host) {
|
||||||
host['updated_on_text'] = moment(host['updated_on'], "x").fromNow();
|
host['updated_on_text'] = moment(host['updated_on'], "x").fromNow();
|
||||||
|
host['wildcard_expires_text'] = moment(host['wildcard_expires'], "x").fromNow();
|
||||||
host['targetssl_text'] = host['targetssl'] ? 'https://' : 'http://';
|
host['targetssl_text'] = host['targetssl'] ? 'https://' : 'http://';
|
||||||
host['forcessl_text'] = host['forcessl'] ? 'https://' : 'http://';
|
host['forcessl_text'] = host['forcessl'] ? 'https://' : 'http://';
|
||||||
host['wildcard_text'] = host['is_wildcard'] ? host['wildcard_status'] : 'Auto';
|
host['wildcard_text'] = host['is_wildcard'] ? host['wildcard_status'] : 'Auto';
|
||||||
|
host['wildcard_text_bg'] = 'warning';
|
||||||
|
if(!host['is_wildcard']){
|
||||||
|
host['wildcard_text_bg'] = 'success';
|
||||||
|
}
|
||||||
|
if(host['wildcard_status'] == 'Done'){
|
||||||
|
host['wildcard_text_bg'] = 'success';
|
||||||
|
host['wildcard_text'] = undefined;
|
||||||
|
}
|
||||||
|
if(host['wildcard_status'].includes('failed')){
|
||||||
|
host['wildcard_text_bg'] = 'danger';
|
||||||
|
}
|
||||||
|
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
@@ -94,6 +106,13 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downloadCert(host, type){
|
||||||
|
app.host.getCert({host}, function(error, data){
|
||||||
|
if(error) app.util.actionMessage(error.message, $.scope.hosts.$this, 'danger');
|
||||||
|
app.util.downloadFile(`${host}-${type}.crt`, data[type])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
$editHostForm = $('#addHost').clone();
|
$editHostForm = $('#addHost').clone();
|
||||||
$editHostForm.find('hr.buttonBreak').nextAll().remove();
|
$editHostForm.find('hr.buttonBreak').nextAll().remove();
|
||||||
@@ -121,9 +140,9 @@
|
|||||||
$el.slideUp();
|
$el.slideUp();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.subscribe(/^model:Host/, function(data, topic){
|
// app.subscribe(/^model:Host/, function(data, topic){
|
||||||
console.log(topic, data);
|
// console.log(topic, data);
|
||||||
});
|
// });
|
||||||
|
|
||||||
app.subscribe(/^model:Host:create/, function(data, topic){
|
app.subscribe(/^model:Host:create/, function(data, topic){
|
||||||
let [a,b, action, host] = topic.split(':');
|
let [a,b, action, host] = topic.split(':');
|
||||||
@@ -350,7 +369,7 @@
|
|||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<th>
|
<th>
|
||||||
SSL
|
SSL Expire
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
Host Name
|
Host Name
|
||||||
@@ -365,10 +384,14 @@
|
|||||||
Actions
|
Actions
|
||||||
</th>
|
</th>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody id="hostsTable">
|
<tbody id="hostsTable">
|
||||||
<tr action="api" jq-repeat="hosts" jq-repeat-index='host' style="display:none">
|
<tr action="api" jq-repeat="hosts" jq-repeat-index='host' style="display:none">
|
||||||
<td>
|
<td class="table-{{wildcard_text_bg}}">
|
||||||
{{{ wildcard_text }}}
|
{{{ wildcard_text }}}
|
||||||
|
{{#wildcard_expires}}
|
||||||
|
<span class="momentFromNow" data-date="{{ wildcard_expires }}" >{{wildcard_expires_text}}</span>
|
||||||
|
{{/wildcard_expires}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a target="_blank" href="{{ forcessl_text }}{{ host }}">
|
<a target="_blank" href="{{ forcessl_text }}{{ host }}">
|
||||||
@@ -378,15 +401,42 @@
|
|||||||
<td>
|
<td>
|
||||||
{{{ targetssl_text }}}{{ ip }}:{{ targetPort }}
|
{{{ targetssl_text }}}{{ ip }}:{{ targetPort }}
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden-xs momentFromNow" data-date="{{ updated_on }}">
|
<td class="hidden-xs momentFromNow" data-date="{{ updated_on }}" >
|
||||||
{{ updated_on_text }}
|
{{ updated_on_text }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<!-- <button type="button" class="btn btn-info">
|
|
||||||
<i class="fa-brands fa-expeditedssl"></i>
|
<div class="btn-group" role="group">
|
||||||
Cert
|
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
</button> -->
|
<i class="fa-brands fa-expeditedssl"></i>
|
||||||
|
Certs
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<button type="button" class="dropdown-item" onclick="downloadCert('{{host}}', 'cert_pem')">
|
||||||
|
<i class="fa-solid fa-certificate"></i>
|
||||||
|
Cert
|
||||||
|
<i class="fa-solid fa-file-arrow-down float-end"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button type="button" class="dropdown-item" onclick="downloadCert('{{host}}', 'fullchain_pem')">
|
||||||
|
<i class="fa-solid fa-link"></i>
|
||||||
|
Full Chain
|
||||||
|
<i class="fa-solid fa-file-arrow-down float-end"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button type="button" class="dropdown-item" onclick="downloadCert('{{host}}', 'privkey_pem')">
|
||||||
|
<i class="fa-solid fa-key"></i>
|
||||||
|
Private key
|
||||||
|
<i class="fa-solid fa-file-arrow-down float-end"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="button" onclick="editHost(this, '{{ host }}');" class="btn btn-sm btn-warning">
|
<button type="button" onclick="editHost(this, '{{ host }}');" class="btn btn-sm btn-warning">
|
||||||
<i class="fa-solid fa-pencil"></i>
|
<i class="fa-solid fa-pencil"></i>
|
||||||
Edit
|
Edit
|
||||||
|
|||||||
Reference in New Issue
Block a user