@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const {User} = require('../models/user');
|
||||
const {AuthToken} = require('../models/token');
|
||||
const Table = require('../models');
|
||||
const {User, AuthToken} = Table.models;
|
||||
|
||||
|
||||
class Auth{
|
||||
@@ -35,6 +35,7 @@ class Auth{
|
||||
|
||||
throw this.errors.login();
|
||||
}catch(error){
|
||||
console.log('check error', error);
|
||||
throw this.errors.login();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ const {Auth} = require('../controller/auth');
|
||||
async function auth(req, res, next){
|
||||
try{
|
||||
req.token = await Auth.checkToken(req.header('auth-token'));
|
||||
req.user = await req.token.getUser();
|
||||
req.user = req.token.user;
|
||||
return next();
|
||||
}catch(error){
|
||||
next(error);
|
||||
@@ -15,7 +15,7 @@ async function auth(req, res, next){
|
||||
async function authIO(socket, next){
|
||||
try{
|
||||
let token = await Auth.checkToken(socket.handshake.auth.token || 0);
|
||||
socket.user = await token.getUser();
|
||||
socket.user = token.user;
|
||||
next();
|
||||
}catch(error){
|
||||
next(error);
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require("crypto");
|
||||
|
||||
const conf = require('../conf');
|
||||
const Table = require('../utils/redis_model');
|
||||
const ModelPs = require('../utils/model_pubsub');
|
||||
|
||||
const tldExtract = require('tld-extract').parse_host;
|
||||
|
||||
const providers = {
|
||||
Cloudflare: require('./dns_provider/cloudflare'),
|
||||
DigitalOcean: require('./dns_provider/digitalocean'),
|
||||
PorkBun: require('./dns_provider/porkbun'),
|
||||
};
|
||||
|
||||
class Domain extends Table{
|
||||
static _key = 'domain';
|
||||
static _keyMap = {
|
||||
'created_by': {isRequired: true, type: 'string', min: 3, max: 500},
|
||||
'created_on': {default: function(){return (new Date).getTime()}},
|
||||
'updated_by': {default:"__NONE__", isRequired: false, type: 'string',},
|
||||
'updated_on': {default: function(){return (new Date).getTime()}, always: true},
|
||||
'domain': {isRequired: true, type: 'string'},
|
||||
'dnsProvider_id': {isRequired: true, type: 'string'},
|
||||
'provider': {model: 'DnsProvider', rel:'one', localKey: 'dnsProvider_id'},
|
||||
'zoneId': {isRequired: false, type: 'string'},
|
||||
}
|
||||
|
||||
static async get(domain, ...args){
|
||||
try{
|
||||
domain = tldExtract(domain).domain;
|
||||
}catch{}
|
||||
|
||||
return await super.get(domain, ...args);
|
||||
}
|
||||
|
||||
async getRecords(...args){
|
||||
return await this.provider.api.getRecords(this, ...args);
|
||||
}
|
||||
|
||||
async createRecord(...args){
|
||||
return await this.provider.api.createRecord(this, ...args);
|
||||
}
|
||||
|
||||
async deleteRecords(...args){
|
||||
return await this.provider.api.deleteRecords(this, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
Domain.register(ModelPs(Domain));
|
||||
|
||||
class DnsProvider extends Table{
|
||||
static _key = 'id';
|
||||
static _keyMap = {
|
||||
'created_by': {isRequired: true, type: 'string', min: 3, max: 500},
|
||||
'created_on': {default: function(){return (new Date).getTime()}},
|
||||
'updated_by': {default:"__NONE__", isRequired: false, type: 'string',},
|
||||
'updated_on': {default: function(){return (new Date).getTime()}, always: true},
|
||||
'id': {default: ()=>crypto.randomBytes(8).toString("hex")},
|
||||
'name': {isRequired: true, type: 'string'},
|
||||
'dnsProvider': {isRequired: true, type: 'string'},
|
||||
'domains': {model:'Domain', rel: 'many', remoteKey: 'dnsProvider_id'}
|
||||
}
|
||||
|
||||
static __intraModel(provider){
|
||||
if(!Object.keys(providers).includes(provider)){
|
||||
throw new Error('Invalid DNS provider');
|
||||
}
|
||||
|
||||
let Provider = providers[provider];
|
||||
let _keyMap = {...this._keyMap, ...Provider._keyMap};
|
||||
|
||||
return ({
|
||||
[this.name] : class extends this {
|
||||
static _keyMap = _keyMap;
|
||||
static Provider = Provider;
|
||||
}
|
||||
})[this.name];
|
||||
}
|
||||
|
||||
static async create(data, ...args){
|
||||
let Provider;
|
||||
try{
|
||||
let __intraModel = this.__intraModel(data.dnsProvider);
|
||||
Provider = __intraModel.Provider;
|
||||
|
||||
// This is here test if the given API key is valid
|
||||
let provider = new __intraModel.Provider(data, ...args);
|
||||
let domains = await provider.listDomains();
|
||||
|
||||
let instance = await super.create.call(__intraModel, data, ...args);
|
||||
await instance.updateDomains(domains);
|
||||
|
||||
return instance;
|
||||
}catch(error){
|
||||
if(error.name === 'UnauthorizedDnsApi'){
|
||||
let keys = [];
|
||||
console.log('Provider', Provider)
|
||||
for(let key in Provider._keyMap){
|
||||
keys.push({'key': key, message: 'Invalid Key'})
|
||||
}
|
||||
throw this.errors.ObjectValidateError(keys, "API rejected key");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async get(data, ...args){
|
||||
let instance = await super.get(data, ...args);
|
||||
let __intraModel = this.__intraModel(instance.dnsProvider);
|
||||
|
||||
return await super.get.call(__intraModel, data, ...args);
|
||||
}
|
||||
|
||||
static listProviders(){
|
||||
let out = [];
|
||||
for(let provider in providers){
|
||||
out.push({
|
||||
name: provider,
|
||||
fields: providers[provider]._keyMap,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
get api(){
|
||||
return new this.constructor.Provider(this);
|
||||
}
|
||||
|
||||
async listDomains(){
|
||||
return this.api.listDomains();
|
||||
}
|
||||
|
||||
async updateDomains(domains){
|
||||
domains = domains || await this.listDomains();
|
||||
let currentDomains = this.domains.map(domain => domain.domain);
|
||||
|
||||
|
||||
for(let domain of domains){
|
||||
if(currentDomains.includes(domain.domain)){
|
||||
delete currentDomains[currentDomains.indexOf(domain.domain)];
|
||||
continue;
|
||||
}
|
||||
await Domain.create({
|
||||
created_by: this.created_by,
|
||||
domain: domain.domain,
|
||||
dnsProvider_id: this.id,
|
||||
zoneId: domain.zoneId,
|
||||
});
|
||||
}
|
||||
console.log('currentDomains:', currentDomains)
|
||||
|
||||
for(let domain of currentDomains){
|
||||
if(!domain) continue
|
||||
domain = await Domain.get(domain);
|
||||
await domain.remove();
|
||||
}
|
||||
}
|
||||
|
||||
async remove(){
|
||||
for(let domain of await this.domains){
|
||||
await domain.remove();
|
||||
}
|
||||
let instance = await super.remove();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
toJSON(){
|
||||
return {
|
||||
...super.toJSON(),
|
||||
...this.constructor.Provider.toJSON()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
DnsProvider.register(ModelPs(DnsProvider))
|
||||
|
||||
|
||||
if(require.main === module){(async function(){try{
|
||||
const conf = require('../conf');
|
||||
|
||||
// console.log(await DnsProvider.findall());
|
||||
|
||||
let provider = await DnsProvider.get('e8443e03ac503c7b');
|
||||
|
||||
console.log(await provider.listDomains())
|
||||
|
||||
let domain = await Domain.get('holycore.quest') // pork
|
||||
// let domain = await Domain.get('rm-rf.stream') // DO
|
||||
// let domain = await Domain.get('test.wtf') // CF
|
||||
|
||||
// console.log(await domain.createRecord({type: 'TXT', name: 'apitewefweefwsewft222', data:'hiiiiiii'}))
|
||||
|
||||
let txtRecords = await domain.getRecords({type: 'TXT'});
|
||||
console.log(txtRecords.map(i=>`${i.name}: ${i.data}`))
|
||||
// console.log(await domain.deleteRecords({type: 'TXT'}))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log('IIFE Error:', error);
|
||||
}finally{
|
||||
process.exit(0);
|
||||
}})()}
|
||||
@@ -0,0 +1,148 @@
|
||||
'use strict';
|
||||
|
||||
const axios = require('axios');
|
||||
const {DnsApi} = require('./common');
|
||||
|
||||
//like the options obj will always use domain data and type
|
||||
// change content to data
|
||||
// change name to domain
|
||||
|
||||
|
||||
class CloudFlare extends DnsApi{
|
||||
static _keyMap = {
|
||||
token: {isRequired: true, type: 'string', isPrivate: true, displayName: 'API Token'},
|
||||
}
|
||||
|
||||
static displayName = 'CloudFlare'
|
||||
static displayIconHtml = `
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#f38020"/>
|
||||
<path d="M608.2 592.4c3.1-10.8 1.9-20.7-3.3-28.1-4.8-6.7-12.9-10.6-22.6-11.1l-184.7-2.4c-1.1 0-2.2-.6-2.8-1.5-.6-.9-.7-2.1-.4-3.3.6-1.8 2.4-3.2 4.3-3.3l186.4-2.4c22.1-1 46.1-18.9 54.5-40.8l10.6-27.8c.5-1.2.6-2.4.3-3.6-12-54.3-60.5-94.8-118.4-94.8-53.4 0-98.7 34.5-114.9 82.4-10.5-7.8-23.9-12-38.3-10.6-25.7 2.5-46.2 23.1-48.8 48.8-.6 6.6-.1 13.1 1.4 19.1-41.9 1.2-75.3 35.4-75.3 77.6 0 3.7.3 7.5.8 11.2.3 1.8 1.8 3.1 3.6 3.1h340.9c1.9 0 3.8-1.4 4.3-3.3l2.4-9.2zM667 473.7c-1.6 0-3.4 0-5.1.2-1.2 0-2.2.9-2.7 2.1l-7.2 25c-3.1 10.8-2 20.7 3.3 28.1 4.8 6.7 12.9 10.6 22.7 11.1l39.3 2.4c1.2 0 2.3.6 2.8 1.5.6.9.7 2.3.5 3.3-.6 1.8-2.4 3.2-4.4 3.3l-41 2.4c-22.2 1-46 18.9-54.5 40.8l-3 7.6c-.6 1.5.5 3 2.1 3h140.8c1.6 0 3.1-1 3.6-2.7 2.4-8.7 3.7-17.9 3.7-27.3 0-55.5-45.3-100.8-101-100.8" style="fill:#fff"/>
|
||||
</svg>`
|
||||
// Cloud icon for cloudflare
|
||||
static displayIconUni = ''
|
||||
|
||||
constructor(token){
|
||||
super()
|
||||
this.token = token.token || token;
|
||||
}
|
||||
|
||||
__typeCheck(type){
|
||||
if(!type) return;
|
||||
if(!['A', 'MX', 'CNAME', 'ALIAS', 'TXT', 'NS', 'AAAA', 'SRV', 'TLSA', 'CAA', 'HTTPS', 'SVCB'].includes(type)) throw new Error(`${this.constructor.name} API: Invalid 'type' passed`)
|
||||
}
|
||||
|
||||
async axios(method, ...args){
|
||||
try{
|
||||
let a = axios.create({
|
||||
baseURL: 'https://api.cloudflare.com/client/v4/zones',
|
||||
headers: {Authorization: `Bearer ${this.token}`}
|
||||
});
|
||||
|
||||
return await a[method](...args);
|
||||
}catch(error){
|
||||
if(!error.response) throw error;
|
||||
if(error.response.data && error.response.data.errors[0].code == 10000){
|
||||
throw this.errors.unauthorized();
|
||||
}
|
||||
throw this.errors.other(error.response.status, error.response.data.errors[0].message, error.response.data.errors[0].code, error);
|
||||
}
|
||||
}
|
||||
|
||||
async listDomains(){
|
||||
let res = await this.axios('get');
|
||||
|
||||
for(let domain of res.data.result){
|
||||
domain.domain = domain.name
|
||||
domain.zoneId = domain.id
|
||||
}
|
||||
|
||||
return res.data.result;
|
||||
}
|
||||
|
||||
/*
|
||||
The API and the generic class interface have different opinions of what keys
|
||||
hold what data, the __parseOptions and __pastseRes normal the keys to what
|
||||
the class expects
|
||||
|
||||
What the the API calls it : What the class wants it as.
|
||||
*/
|
||||
__apiKeyMap = {
|
||||
'content': 'data',
|
||||
}
|
||||
|
||||
//get records
|
||||
async getRecords(domain, options){
|
||||
let res = await this.axios('get',
|
||||
`${domain.zoneId}/dns_records`,
|
||||
);
|
||||
let records = this.__parseRes(res.data.result);
|
||||
|
||||
if(!options) return records;
|
||||
|
||||
return records.filter((record)=>{
|
||||
let matchCount = 0
|
||||
for(let key in options){
|
||||
if(record[key] === options[key] && ++matchCount === Object.keys(options).length){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async createRecord(domain, options){
|
||||
try{
|
||||
let res = await this.axios('post',
|
||||
`${domain.zoneId}/dns_records`,
|
||||
this.__parseOptions(options, ['type', 'name', 'data'])
|
||||
);
|
||||
|
||||
return this.__parseRes([res.data.result])[0];
|
||||
}catch(error){
|
||||
if(error.APIcode == 81058){
|
||||
return (await this.getRecords(domain, options))[0];
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async deleteRecordById(domain, id){
|
||||
let res = await this.axios('delete', `${domain.zoneId}/dns_records/${id}`);
|
||||
}
|
||||
|
||||
async deleteRecords(domain, options){
|
||||
let records = await this.getRecords(domain, options)
|
||||
for(let record of records){
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CloudFlare;
|
||||
|
||||
|
||||
if(require.main === module){(async function(){try{
|
||||
// let cf = new CloudFlare("");
|
||||
// let domain = {
|
||||
// domain: "example.uk",
|
||||
// zoneId: "5eb25c12cd7d22f11252330a29a0dd77"
|
||||
// }
|
||||
|
||||
// console.log(await cf.listDomains())
|
||||
|
||||
//content = ip
|
||||
//name = domain
|
||||
// console.log('get', await cf.getRecords(domain, {content: '172.206.221.130'}))
|
||||
|
||||
// console.log('post', await cf.createRecord(domain, {name:'test', content: '10.0.0.1', type: "TXT"}))
|
||||
|
||||
// console.log('delete', await cf.deleteRecordById(domain , "5c0e958c3406a34d011459933d538b78"))
|
||||
|
||||
// console.log('delete', await cf.deleteRecords(domain, {type: 'A'}))
|
||||
|
||||
}catch(error){
|
||||
console.log('IIFE Error:', error)
|
||||
}})()}
|
||||
@@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
|
||||
const tldExtract = require('tld-extract').parse_host;
|
||||
|
||||
class DnsApi{
|
||||
errors = {
|
||||
unauthorized: ()=>{
|
||||
let error = new Error('UnauthorizedDnsApi');
|
||||
error.name = 'UnauthorizedDnsApi';
|
||||
error.message = `Unauthorized call to ${this.constructor.name}`;
|
||||
error.status = 424;
|
||||
|
||||
return error;
|
||||
},
|
||||
invalidInput: (keys)=>{
|
||||
let error = new Error('InvalidInput');
|
||||
error.name = 'InvalidInput';
|
||||
error.message = `Required keys missing: ${keys.join(', ')}`
|
||||
|
||||
return error
|
||||
|
||||
},
|
||||
other: (status, message, APIcode)=>{
|
||||
let error = new Error('OtherDnsApiError');
|
||||
error.name = 'OtherDnsApiError';
|
||||
error.message = `DNS API Error ${this.constructor.name}: ${status} ${message}`;
|
||||
error.status = 424;
|
||||
error.APIcode = APIcode;
|
||||
return error;
|
||||
},
|
||||
}
|
||||
|
||||
static info(){
|
||||
let svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(this.displayIconHtml)
|
||||
.replace(/'/g, '%27')
|
||||
.replace(/"/g, '%22')}`
|
||||
|
||||
return {
|
||||
displayName: this.displayName,
|
||||
displayIconUni: this.displayIconUni,
|
||||
displayIconHtml: svgDataUrl,
|
||||
fields: this._keyMap,
|
||||
}
|
||||
}
|
||||
|
||||
static toJSON(){
|
||||
return {
|
||||
...this.info(),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
No instance data should ever be shared, so just give the static level inf
|
||||
*/
|
||||
toJSON(){
|
||||
return this.constructor.toJSON();
|
||||
}
|
||||
|
||||
|
||||
__typeCheck(type){
|
||||
if(!['A', 'MX', 'CNAME', 'ALIAS', 'TXT', 'NS', 'AAAA', 'SRV', 'TLSA', 'CAA', 'HTTPS', 'SVCB'].includes(type)) throw new Error('PorkBun API: Invalid type passed')
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
The API and the generic class interface have different opinions of what keys
|
||||
hold what data, the __parseOptions and __pastseRes normal the keys to what
|
||||
the class expects
|
||||
|
||||
What the the API calls it : What the class wants it as.
|
||||
*/
|
||||
__apiKeyMap = {};
|
||||
|
||||
__parseOptions(options, keys){
|
||||
if(!options && !keys) return undefined;
|
||||
|
||||
if(keys){
|
||||
let missingKeys = []
|
||||
for(let key of keys){
|
||||
if(!options[key]) missingKeys.push(key)
|
||||
}
|
||||
|
||||
if(missingKeys.length) throw this.errors.invalidInput(missingKeys);
|
||||
}
|
||||
|
||||
for(let [apiKey, clsKey] of Object.entries(this.__apiKeyMap)){
|
||||
if(options[clsKey]){
|
||||
options[apiKey] = options[clsKey];
|
||||
delete options[clsKey];
|
||||
}
|
||||
}
|
||||
|
||||
if(options.type) this.__typeCheck(options.type);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
__parseRes(data){
|
||||
for(let item of data){
|
||||
for(let [apiKey, clsKey] of Object.entries(this.__apiKeyMap)){
|
||||
if(item[apiKey]){
|
||||
item[clsKey] = item[apiKey];
|
||||
}
|
||||
}
|
||||
try{
|
||||
item.name = tldExtract(item.name).sub
|
||||
}catch{}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DnsApi,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
const axios = require('axios');
|
||||
const {DnsApi} = require('./common');
|
||||
|
||||
class DigitalOcean extends DnsApi{
|
||||
static _keyMap = {
|
||||
token: {isRequired: true, type: 'string', isPrivate: true, displayName: 'API Token'},
|
||||
}
|
||||
|
||||
static displayName = 'DigitalOcean'
|
||||
static displayIconHtml = `
|
||||
<svg height="100%" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#0080ff"/>
|
||||
<path d="m273.8 669.2-.1-63.7h63.7v63.7h76v-98.8h98.8v98.5c105.1-.1 186.2-104.1 146.1-214.6-14.9-40.9-47.6-73.6-88.5-88.4-110.7-40.2-214.7 41.2-214.7 146.3H256c0-167.5 161.8-298 337.4-243.2 76.8 24 137.7 84.9 161.6 161.6C809.9 606.2 679.4 768 511.9 768v-98.8h-98.6v75.9h-75.9v-75.9h-63.6z" style="fill:#fff"/>
|
||||
</svg>`
|
||||
// '<i class="fa-brands fa-digital-ocean"></i>'
|
||||
static displayIconUni = ''
|
||||
|
||||
constructor(token){
|
||||
super()
|
||||
this.token = token.token || token;
|
||||
}
|
||||
|
||||
async axios(method, ...args){
|
||||
try{
|
||||
let a = axios.create({
|
||||
baseURL: 'https://api.digitalocean.com/v2/',
|
||||
headers: {Authorization: `Bearer ${this.token}`}
|
||||
});
|
||||
|
||||
return await a[method](...args);
|
||||
}catch(error){
|
||||
if(!error.response) throw error;
|
||||
if(error.response.data && error.response.data.id === 'Unauthorized'){
|
||||
throw this.errors.unauthorized();
|
||||
}
|
||||
throw this.errors.other(error.response.status, error.response.data.message);
|
||||
}
|
||||
}
|
||||
|
||||
async listDomains(){
|
||||
let res = await this.axios('get', '/domains');
|
||||
|
||||
return this.__parseRes(res.data.domains);
|
||||
}
|
||||
|
||||
async getRecords(domain, options){
|
||||
options = this.__parseOptions(options);
|
||||
let res = await this.axios('get', `/domains/${domain.domain}/records`, {params: options})
|
||||
let records = this.__parseRes(res.data.domain_records);
|
||||
if(!options) return records;
|
||||
|
||||
return records.filter((record)=>{
|
||||
let matchCount = 0
|
||||
for(let key in options){
|
||||
if(record[key] === options[key] && ++matchCount === Object.keys(options).length){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async createRecord(domain, options){
|
||||
options = this.__parseOptions(options, ['type', 'name', 'data']);
|
||||
let res = await this.axios('post', `/domains/${domain.domain}/records`, options);
|
||||
|
||||
return this.__parseRes([res.data.domain_record])[0];
|
||||
}
|
||||
|
||||
async deleteRecordById(domain, id){
|
||||
let res = await this.axios('delete', `/domains/${domain.domain}/records/${id}`);
|
||||
}
|
||||
|
||||
async deleteRecords(domain, options){
|
||||
let records = await this.getRecords(domain, options)
|
||||
for(let record of records){
|
||||
let res = await this.deleteRecordById(domain, record.id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DigitalOcean;
|
||||
@@ -0,0 +1,133 @@
|
||||
'use strict';
|
||||
|
||||
const axios = require('axios');
|
||||
const {DnsApi} = require('./common');
|
||||
|
||||
|
||||
class PorkBun extends DnsApi{
|
||||
static _keyMap = {
|
||||
'apiKey': {isRequired: true, type: 'string', isPrivate: true, displayName: 'API key'},
|
||||
'secretApiKey': {isRequired: true, type: 'string', isPrivate: true, displayName: 'API Secret key'},
|
||||
}
|
||||
|
||||
static displayName = 'PorkBun';
|
||||
static displayIconUni = '';
|
||||
static displayIconHtml = `
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.st1{fill:#fff}
|
||||
</style>
|
||||
<g id="Icon">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#ef7878"/>
|
||||
<g id="Logo">
|
||||
<path class="st1" d="M398.3 331.8c-33.2-17.9-70.3-31.9-108.6-40.9-7.7 16.6-11.5 33.2-11.5 52.4 0 28.1 8.9 53.7 24.3 74.1 24.2-35.7 56.2-66.4 95.8-85.6zm323.3 85.6c15.3-20.4 24.3-46 24.3-74.1 0-19.2-3.8-37.1-11.5-52.4-38.3 7.7-75.4 21.7-108.6 40.9 38.3 19.2 71.5 49.9 95.8 85.6zm-152.1 58.8c-7.7 0-14.1 6.4-14.1 14.1 0 2.6 1.3 5.1 2.6 7.7 5.1 7.7 12.8 12.8 21.7 15.3 2.6-5.1 3.8-11.5 3.8-17.9V489c-1.2-7.7-6.3-12.8-14-12.8z"/>
|
||||
<path class="st1" d="M503.1 320.3c-126.5 5.1-224.9 112.4-224.9 239v131.6c0 23 19.2 42.2 42.2 42.2 23 0 42.2-19.2 42.2-42.2v-34.5H659v34.5c0 23 19.2 42.2 42.2 42.2 23 0 42.2-19.2 42.2-42.2v-138c1.2-131.6-107.5-237.7-240.3-232.6zm132.8 184c-7.7 12.8-19.2 21.7-33.2 26.8-8.9 17.9-28.1 30.7-49.8 30.7h-6.4c-7.7 0-14.1-6.4-14.1-14.1s6.4-14.1 14.1-14.1c6.4 0 12.8-2.6 17.9-5.1-7.7-3.8-15.3-8.9-20.4-16.6-5.1-6.4-7.7-12.8-7.7-21.7 0-17.9 15.3-33.2 33.2-33.2 11.5 0 20.4 5.1 26.8 14.1 7.7 10.2 12.8 21.7 12.8 35.8v5.1c6.4-2.6 11.5-7.7 15.3-12.8 2.6-3.8 6.4-3.8 10.2-2.6 2.6-1.2 3.9 3.9 1.3 7.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>`
|
||||
|
||||
constructor(args){
|
||||
super()
|
||||
this.apiKey = args.apiKey;
|
||||
this.secretApiKey = args.secretApiKey;
|
||||
}
|
||||
|
||||
async post(url, data){
|
||||
let res;
|
||||
try{
|
||||
data = {
|
||||
...(data || {}),
|
||||
secretapikey: this.secretApiKey,
|
||||
apikey: this.apiKey,
|
||||
};
|
||||
res = await axios.post(`https://api.porkbun.com/api/json/v3${url}`, data);
|
||||
|
||||
return res;
|
||||
}catch(error){
|
||||
if(!error.response) throw error;
|
||||
if(error.response.data.message.includes('Invalid API key')){
|
||||
throw this.errors.unauthorized();
|
||||
}
|
||||
// console.error('API error:', error)
|
||||
throw this.errors.other(error.response.status, error.response.data.message)
|
||||
}
|
||||
}
|
||||
|
||||
__typeCheck(type){
|
||||
if(!['A', 'MX', 'CNAME', 'ALIAS', 'TXT', 'NS', 'AAAA', 'SRV', 'TLSA', 'CAA', 'HTTPS', 'SVCB'].includes(type)) throw new Error('PorkBun API: Invalid type passed')
|
||||
}
|
||||
|
||||
__parseName(domain, name){
|
||||
if(name && !name.endsWith('.'+domain)){
|
||||
return `${name}.${domain}`
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
The API and the generic class interface have different opinions of what keys
|
||||
hold what data, the __parseOptions and __pastseRes normal the keys to what
|
||||
the class expects
|
||||
|
||||
What the the API calls it : What the class wants it as.
|
||||
*/
|
||||
|
||||
__apiKeyMap = {
|
||||
'content': 'data'
|
||||
}
|
||||
|
||||
async getRecords(domain, options){
|
||||
let res = await this.post(`/dns/retrieve/${domain}`);
|
||||
let records = this.__parseRes(res.data.records)
|
||||
if(!options) return records;
|
||||
options = this.__parseOptions(options);
|
||||
|
||||
return records.filter((record)=>{
|
||||
let matchCount = 0
|
||||
for(let key in options){
|
||||
if(record[key] === options[key] && ++matchCount === Object.keys(options).length){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async createRecord(domain, options, force){
|
||||
if(force){
|
||||
await this.deleteRecords(domain, options)
|
||||
}
|
||||
|
||||
try{
|
||||
options = this.__parseOptions(options, ['type', 'name', 'data']);
|
||||
let res = await this.post(`/dns/create/${domain}`, options);
|
||||
|
||||
return res.data.result;
|
||||
}catch(error){
|
||||
if(error.message && error.message.includes('We were unable to create the DNS record')){
|
||||
return (await this.getRecords(domain, options))[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteRecordById(domain, id){
|
||||
let res = await this.post(`/dns/delete/${domain.domain}/${id}`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
|
||||
async deleteRecords(domain, options){
|
||||
let records = await this.getRecords(domain, options);
|
||||
for(let record of records){
|
||||
await this.deleteRecordById(domain, record.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async listDomains(){
|
||||
let res = await this.post(`/domain/listAll`, {"includeLabels": "yes"});
|
||||
return res.data.domains;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PorkBun;
|
||||
+36
-17
@@ -1,19 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const Table = require('../utils/redis_model');
|
||||
const Table = require('.');
|
||||
const {Domain} = require('.').models;
|
||||
const ModelPs = require('../utils/model_pubsub');
|
||||
|
||||
const tldExtract = require('tld-extract').parse_host;
|
||||
const PorkBun = require('../utils/porkbun');
|
||||
const LetsEncrypt = require('../utils/letsencrypt');
|
||||
const conf = require('../conf');
|
||||
|
||||
let porkBun = new PorkBun(conf.porkBun.apiKey, conf.porkBun.secretApiKey);
|
||||
let letsEncrypt = new LetsEncrypt({
|
||||
directoryUrl: LetsEncrypt.AcmeClient.directory.letsencrypt.staging,
|
||||
const letsEncrypt = new LetsEncrypt({
|
||||
directoryUrl: conf.environment === "production" ?
|
||||
LetsEncrypt.AcmeClient.directory.letsencrypt.production :
|
||||
LetsEncrypt.AcmeClient.directory.letsencrypt.staging,
|
||||
});
|
||||
|
||||
|
||||
class Host extends Table{
|
||||
static _key = 'host';
|
||||
static _keyMap = {
|
||||
@@ -28,9 +28,10 @@ class Host extends Table{
|
||||
'targetssl': {isRequired: false, default: false, type: 'boolean'},
|
||||
'is_cache': {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},
|
||||
'wildcard_parent': {isRequired: false, type: 'string', min: 3, max: 500},
|
||||
'wildcard_expires': {isRequired: false, type: 'number'},
|
||||
'domain': {model: 'Domain', rel: 'one'},
|
||||
}
|
||||
|
||||
static lookUpObj = {};
|
||||
@@ -38,7 +39,6 @@ class Host extends Table{
|
||||
|
||||
static async addCache(host, parentOBJ){
|
||||
try{
|
||||
|
||||
console.log('addCache host:', host, 'parentOBJ host', parentOBJ.host)
|
||||
parentOBJ = await this.get(parentOBJ.host);
|
||||
|
||||
@@ -88,6 +88,8 @@ class Host extends Table{
|
||||
|
||||
static async create(data, ...args){
|
||||
try{
|
||||
if(data.is_wildcard) await this.validateWildcardCreate(data, args);
|
||||
|
||||
let out = await super.create(data, ...args);
|
||||
await this.buildLookUpObj();
|
||||
if(out.is_wildcard) out.createWildcardCert();
|
||||
@@ -99,6 +101,17 @@ class Host extends Table{
|
||||
}
|
||||
}
|
||||
|
||||
static async validateWildcardCreate(data, ...args){
|
||||
try{
|
||||
if(!data.host.startsWith('*.')) throw new Error('not wild card');
|
||||
await Domain.get(data.host);
|
||||
}catch(error){
|
||||
console.log('validateWildcardCreate error', error)
|
||||
if(error.status === 404) error.message = "No matching DNS provider registered"
|
||||
throw this.errors.ObjectValidateError([{key: 'host', message: error.message}]);
|
||||
}
|
||||
}
|
||||
|
||||
async createWildcardCert(){
|
||||
if(!this.host.startsWith('*.')) throw new Error('not wild card');
|
||||
|
||||
@@ -116,12 +129,11 @@ class Host extends Table{
|
||||
try{
|
||||
let parts = tldExtract(authz.identifier.value);
|
||||
|
||||
let res = await porkBun.createRecordForce(
|
||||
parts.domain,
|
||||
let res = await host.domain.createRecord(
|
||||
{
|
||||
type:'TXT',
|
||||
name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`,
|
||||
content: `${keyAuthorization}`
|
||||
data: `${keyAuthorization}`
|
||||
}
|
||||
);
|
||||
}catch(error){
|
||||
@@ -162,8 +174,7 @@ class Host extends Table{
|
||||
})
|
||||
try{
|
||||
let parts = tldExtract(authz.identifier.value);
|
||||
await porkBun.deleteRecords(
|
||||
parts.domain,
|
||||
await host.domain.deleteRecords(
|
||||
{
|
||||
type:'TXT',
|
||||
name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''
|
||||
@@ -322,6 +333,7 @@ class Host extends Table{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Host.register(ModelPs(Host))
|
||||
|
||||
|
||||
class Cached extends Table{
|
||||
@@ -338,9 +350,16 @@ class Cached extends Table{
|
||||
|
||||
module.exports = {Host: ModelPs(Host)};
|
||||
|
||||
(async function(){
|
||||
if(require.main === module){(async function(){
|
||||
try{
|
||||
await Host.lookUpReady();
|
||||
|
||||
let host = await Host.get('*.new.test.wtf')
|
||||
|
||||
console.log('host', host.domain.provider.api);
|
||||
|
||||
|
||||
|
||||
// let res = await Host.create({
|
||||
// host: '*.test.holycore.quest',
|
||||
// ip: '192.168.1.47',
|
||||
@@ -353,8 +372,8 @@ try{
|
||||
// console.log('IIFE res:\n', res)
|
||||
|
||||
// console.log(Host.test(55))
|
||||
console.log(await Host.list())
|
||||
console.log(await Cached.listDetail())
|
||||
// console.log(await Host.list())
|
||||
// console.log(await Cached.listDetail())
|
||||
// console.log('IIFE lookup:', Host.lookUp('bld3324sdf.test.holycore.quest'))
|
||||
|
||||
|
||||
@@ -389,4 +408,4 @@ try{
|
||||
}catch(error){
|
||||
console.log('IIFE test area error:', error)
|
||||
}
|
||||
})()
|
||||
})()}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const Table = require('../utils/redis_model')
|
||||
module.exports = Table;
|
||||
|
||||
require('./dns_provider');
|
||||
require('./host');
|
||||
require('./token');
|
||||
require('./user');
|
||||
+10
-11
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const Table = require('../utils/redis_model');
|
||||
const {User} = require('./user');
|
||||
const Table = require('.');
|
||||
const UUID = function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,b)};
|
||||
|
||||
|
||||
@@ -11,8 +10,8 @@ class Token extends Table{
|
||||
'created_by': {isRequired: true, type: 'string', min: 3, max: 500},
|
||||
'created_on': {default: function(){return (new Date).getTime()}},
|
||||
'updated_on': {default: function(){return (new Date).getTime()}, always: true},
|
||||
'token': {default: UUID, type: 'string', min: 36, max: 36},
|
||||
'is_valid': {default: true, type: 'boolean'}
|
||||
'token': {default: UUID, type: 'string', min: 36, max: 36, isPrivate: true},
|
||||
'is_valid': {default: true, type: 'boolean'},
|
||||
}
|
||||
|
||||
constructor(...args){
|
||||
@@ -28,13 +27,12 @@ class Token extends Table{
|
||||
}
|
||||
}
|
||||
|
||||
class AuthToken extends Token{
|
||||
constructor(...args){
|
||||
super(...args);
|
||||
}
|
||||
Token.register();
|
||||
|
||||
async getUser(){
|
||||
return await User.get(this.created_by);
|
||||
class AuthToken extends Token{
|
||||
static _keyMap = {
|
||||
...super._keyMap,
|
||||
user: {model: 'User', rel: 'one', localKey: 'created_by'},
|
||||
}
|
||||
|
||||
static async create(data){
|
||||
@@ -42,8 +40,8 @@ class AuthToken extends Token{
|
||||
return super.create(data)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
AuthToken.register();
|
||||
|
||||
class InviteToken extends Token{
|
||||
static _keyMap = {
|
||||
@@ -66,5 +64,6 @@ class InviteToken extends Token{
|
||||
}
|
||||
}
|
||||
}
|
||||
InviteToken.register();
|
||||
|
||||
module.exports = {Token, InviteToken, AuthToken};
|
||||
|
||||
@@ -12,7 +12,7 @@ class User extends Table{
|
||||
'updated_by': {default:"__NONE__", isRequired: false, type: 'string',},
|
||||
'updated_on': {default: function(){return (new Date).getTime()}, always: true},
|
||||
'username': {isRequired: true, type: 'string', min: 3, max: 500},
|
||||
'password': {isRequired: true, type: 'string', min: 3, max: 500},
|
||||
'password': {isRequired: true, type: 'string', min: 3, max: 500, isPrivate: true},
|
||||
'backing': {default:"redis", isRequired: false, type: 'string',},
|
||||
}
|
||||
|
||||
@@ -58,12 +58,9 @@ class User extends Table{
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = {User};
|
||||
|
||||
User.register();
|
||||
|
||||
(async function(){
|
||||
var defaultUser = 'proxyadmin2'
|
||||
|
||||
Generated
+1
-1
@@ -10,6 +10,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"acme-client": "^5.4.0",
|
||||
"axios": "^1.7.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
@@ -197,7 +198,6 @@
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"acme-client": "^5.4.0",
|
||||
"axios": "^1.7.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
body {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
nav.navbar{
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
div.card-body{
|
||||
padding-left: 1.5em;
|
||||
padding-right: 1.5em;
|
||||
#spa-shell {
|
||||
margin-top: 4.5rem;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
#spa-shell {
|
||||
padding-top: 4.5rem;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
.card-title{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,8 @@ app.api = (function(app){
|
||||
var baseURL = '/api/'
|
||||
|
||||
function post(url, data, callback){
|
||||
$.ajax({
|
||||
if(!$.isFunction(callback)) callback = callback2;
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
url: baseURL+url,
|
||||
headers:{
|
||||
@@ -86,17 +87,18 @@ app.api = (function(app){
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
complete: function(res, text){
|
||||
callback(
|
||||
callback ? callback(
|
||||
text !== 'success' ? res.statusText : null,
|
||||
JSON.parse(res.responseText),
|
||||
res.status
|
||||
)
|
||||
) : function(){}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function put(url, data, callback){
|
||||
$.ajax({
|
||||
if(!$.isFunction(callback)) callback = callback2;
|
||||
return $.ajax({
|
||||
type: 'PUT',
|
||||
url: baseURL+url,
|
||||
headers:{
|
||||
@@ -106,18 +108,18 @@ app.api = (function(app){
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
complete: function(res, text){
|
||||
callback(
|
||||
callback ? callback(
|
||||
text !== 'success' ? res.statusText : null,
|
||||
JSON.parse(res.responseText),
|
||||
res.status
|
||||
)
|
||||
) : function(){}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function remove(url, callback, callback2){
|
||||
if(!$.isFunction(callback)) callback = callback2;
|
||||
$.ajax({
|
||||
return $.ajax({
|
||||
type: 'delete',
|
||||
url: baseURL+url,
|
||||
headers:{
|
||||
@@ -126,17 +128,36 @@ app.api = (function(app){
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
complete: function(res, text){
|
||||
callback(
|
||||
callback ? callback(
|
||||
text !== 'success' ? res.statusText : null,
|
||||
JSON.parse(res.responseText),
|
||||
res.status
|
||||
)
|
||||
) : function(){}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function options(url, callback){
|
||||
return $.ajax({
|
||||
type: 'OPTIONS',
|
||||
url: baseURL+url,
|
||||
headers:{
|
||||
'auth-token': app.auth.getToken()
|
||||
},
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
complete: function(res, text){
|
||||
callback ? callback(
|
||||
text !== 'success' ? res.statusText : null,
|
||||
JSON.parse(res.responseText),
|
||||
res.status
|
||||
) : function(){}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function get(url, callback){
|
||||
$.ajax({
|
||||
return $.ajax({
|
||||
type: 'GET',
|
||||
url: baseURL+url,
|
||||
headers:{
|
||||
@@ -145,16 +166,16 @@ app.api = (function(app){
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
complete: function(res, text){
|
||||
callback(
|
||||
callback ? callback(
|
||||
text !== 'success' ? res.statusText : null,
|
||||
JSON.parse(res.responseText),
|
||||
res.status
|
||||
)
|
||||
) : function(){}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {post: post, get: get, put: put, delete: remove}
|
||||
return {post: post, get: get, put: put, delete: remove, options: options,}
|
||||
})(app)
|
||||
|
||||
app.auth = (function(app){
|
||||
@@ -342,7 +363,12 @@ $( document ).ready(function(){
|
||||
});
|
||||
|
||||
$('.fa-circle-minus').click(function(){
|
||||
$(this).closest('.card').find('.card-body').slideToggle('fast');
|
||||
let $body = $(this).closest('.card').find('.card-body');
|
||||
if($body.hasClass('d-none')){
|
||||
$body.removeClass("d-none").removeClass('d-md-block');
|
||||
if($body.is(":visible")) $body.hide();
|
||||
}
|
||||
$body.slideToggle('fast');
|
||||
});
|
||||
|
||||
$('.fa-circle-xmark').click(function(){
|
||||
@@ -363,6 +389,17 @@ $( document ).ready(function(){
|
||||
}, 30000,);
|
||||
});
|
||||
|
||||
(function($){
|
||||
$.fn.scrollTo = function(){
|
||||
const yOffset = Number($('#spa-shell').css('margin-top').replace('px', ''));
|
||||
const y = this[0].getBoundingClientRect().top + window.scrollY - yOffset;
|
||||
|
||||
console.log('y', y)
|
||||
window.scrollTo({top: y, behavior: 'smooth'});
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
||||
//ajax form submit
|
||||
function formAJAX(btn){
|
||||
event.preventDefault(btn); // avoid to execute the actual submit of the form.
|
||||
|
||||
@@ -6,11 +6,22 @@ MIT license
|
||||
|
||||
(function($, Mustache){
|
||||
'use strict';
|
||||
if (!$.scope) {
|
||||
$.scope = {};
|
||||
}
|
||||
|
||||
var make = function(element){
|
||||
var scope = {};
|
||||
|
||||
$.scope = new Proxy(scope, {
|
||||
get(obj, prop){
|
||||
if(!obj[prop]){
|
||||
scope[prop] = [];
|
||||
}
|
||||
return Reflect.get(...arguments);
|
||||
},
|
||||
set(obj, prop, value) {
|
||||
|
||||
return Reflect.set(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
var make = function(element, template){
|
||||
var result = [];
|
||||
|
||||
result.splice = function(inputValue, ...args){
|
||||
@@ -82,7 +93,7 @@ MIT license
|
||||
//figure out new elements index
|
||||
var key = I + index;
|
||||
// apply values to template
|
||||
var render = Mustache.render(this.__jqTemplate, toAdd[I]);
|
||||
var render = Mustache.render(this.__jqTemplate, this.__buildData(i, toAdd[I]));
|
||||
|
||||
//set call name and index keys to DOM element
|
||||
var $render = $( render ).addClass( 'jq-repeat-'+ this.__jqRepeatId ).attr( 'jq-repeat-index', key );
|
||||
@@ -217,10 +228,10 @@ MIT license
|
||||
}
|
||||
this[index] = $.extend( true, this[index], data );
|
||||
|
||||
var $render = $(Mustache.render(this.__jqTemplate, this[index]));
|
||||
var $render = $(Mustache.render(this.__jqTemplate, this.__buildData(index, this[index])));
|
||||
$render.attr('jq-repeat-index', index);
|
||||
|
||||
this.__update(this[index].__jq_$el, $render, this[index], this);
|
||||
this.__putUpdate(this[index].__jq_$el, $render, this[index], this);
|
||||
this[index].__jq_$el = $render;
|
||||
};
|
||||
|
||||
@@ -228,6 +239,8 @@ MIT license
|
||||
return this[this.indexOf(key, value)];
|
||||
}
|
||||
|
||||
// User definable helper methods
|
||||
|
||||
result.__put = function($el, item, list){
|
||||
$el.show();
|
||||
};
|
||||
@@ -236,42 +249,76 @@ MIT license
|
||||
$el.remove();
|
||||
};
|
||||
|
||||
result.__update = function($el, $render, item, list){
|
||||
result.__putUpdate = function($el, $render, item, list){
|
||||
$el.replaceWith($render);
|
||||
$el.show();
|
||||
};
|
||||
|
||||
result.__setPut = function(fn) {
|
||||
Object.defineProperty(this, '__put', {
|
||||
value: fn,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
result.__parseData = function(data){
|
||||
return data;
|
||||
}
|
||||
|
||||
// internal helper methods
|
||||
|
||||
result.__buildData = function(index, data){
|
||||
return {
|
||||
...this.__parseData(data),
|
||||
nestedTemplates: this.__parseNestedTemplates(index, data),
|
||||
_parent: this._parentData,
|
||||
};
|
||||
};
|
||||
|
||||
result.__setTake = function(fn) {
|
||||
Object.defineProperty(this, '__take', {
|
||||
value: fn,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
};
|
||||
result.__parseNestedTemplates = function(index, data){
|
||||
let templates = []
|
||||
let tempData = {
|
||||
...data,
|
||||
_parent: data,
|
||||
};
|
||||
|
||||
result.__setUpdate = function(fn) {
|
||||
Object.defineProperty(this, '__update', {
|
||||
value: fn,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
};
|
||||
for(let idx in this.nestedTemplates){
|
||||
let $el = $(`${this.nestedTemplates[idx]}`);
|
||||
|
||||
var $this = $( element );
|
||||
$el.attr('jq-repeat', Mustache.render($el.attr('jq-repeat'), tempData));
|
||||
$el.attr('jq-repeat-index', Mustache.render($el.attr('jq-repeat-index'), tempData));
|
||||
$el.attr('jq-repeat-parent', this.__jqRepeatId);
|
||||
$el.attr('jq-repeat-parent-index', index);
|
||||
templates[idx] = $el[0].outerHTML;
|
||||
}
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
for(let prop of ['put', 'take', 'putUpdate', 'parseData']){
|
||||
Object.defineProperty(result, prop, {
|
||||
enumerable: false,
|
||||
get(){
|
||||
return this[`__${prop}`]
|
||||
},
|
||||
set(value) {
|
||||
this[`__${prop}`] = value;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
var $this = $(element);
|
||||
result.nestedTemplates = [];
|
||||
result.__jqRepeatId = $this.attr( 'jq-repeat' );
|
||||
$this.removeAttr('jq-repeat');
|
||||
result.__index = $this.attr('jq-repeat-index');
|
||||
|
||||
if($this.attr('jq-repeat-parent')){
|
||||
result._parentData = $.scope[$this.attr('jq-repeat-parent')][$this.attr('jq-repeat-parent-index')]
|
||||
result.__jqParent = $this.attr('jq-repeat-parent');
|
||||
result.__jqParentIndex = $this.attr('jq-repeat-parent-index');
|
||||
}
|
||||
|
||||
$this.find('[jq-repeat]').each((idx, el)=>{
|
||||
let templateIdx = result.nestedTemplates.length;
|
||||
let template = `${el.outerHTML}`;
|
||||
result.nestedTemplates.push(template);
|
||||
$(el).replaceWith(`{{{ nestedTemplates.${templateIdx} }}}`);
|
||||
});
|
||||
|
||||
result.__jqTemplate = $this[0].outerHTML;
|
||||
$this.replaceWith( '<script type="x-tmpl-mustache" id="jq-repeat-holder-' + result.__jqRepeatId + '"><\/script>' );
|
||||
result.$this = $('#jq-repeat-holder-' + result.__jqRepeatId);
|
||||
@@ -287,24 +334,61 @@ MIT license
|
||||
});
|
||||
}
|
||||
|
||||
var temp = $.scope[result.__jqRepeatId] || [];
|
||||
$.scope[result.__jqRepeatId] = result;
|
||||
|
||||
for(let prop of Object.keys(temp)){
|
||||
if(prop,Number.isInteger(Number(prop))){
|
||||
$.scope[result.__jqRepeatId].push(temp[prop]);
|
||||
}else{
|
||||
$.scope[result.__jqRepeatId][prop] = temp[prop];
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$( document ).ready( function(){
|
||||
$( '[jq-repeat]' ).each(function(key, value){
|
||||
make(value);
|
||||
|
||||
// Create an instance of MutationObserver and pass the callback function
|
||||
const observer = new MutationObserver(function(mutationsList) {
|
||||
mutationsList.forEach(mutation => {
|
||||
if (mutation.type === 'childList') {
|
||||
const addedNodes = mutation.addedNodes;
|
||||
addedNodes.forEach(node => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const $el = $(node);
|
||||
if ($el.is('[jq-repeat]')) {
|
||||
make(node);
|
||||
} else {
|
||||
const toMake = [];
|
||||
$el.find('[jq-repeat]').each((key, el) => {
|
||||
if ($(el).parent().closest('[jq-repeat]').length) return;
|
||||
toMake.push(el);
|
||||
});
|
||||
|
||||
toMake.forEach(el => {
|
||||
make(el);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('DOMNodeInserted', function(e) {
|
||||
if ( $(e.target).is('[jq-repeat]') ){
|
||||
make( e.target );
|
||||
}else{
|
||||
var t = $(e.target).find('[jq-repeat]');
|
||||
t.each(function(key, value){
|
||||
make(value);
|
||||
});
|
||||
}
|
||||
// Start observing the document
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
let toMake = [];
|
||||
|
||||
$( '[jq-repeat]' ).each(function(key, value){
|
||||
if($(value).parent().closest('[jq-repeat]').length) return;
|
||||
toMake.push(value);
|
||||
});
|
||||
|
||||
for(let el of toMake){
|
||||
make(el);
|
||||
}
|
||||
} );
|
||||
|
||||
})(jQuery, Mustache);
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
var value = this.val(); //link to input value
|
||||
var message;
|
||||
|
||||
if(this.prop('disabled')) return true;
|
||||
|
||||
|
||||
//checks if field is required, and length
|
||||
if(!isNaN(options) && value.length < options){
|
||||
message = `Must be ${options} characters`;
|
||||
|
||||
@@ -13,6 +13,8 @@ router.use('/user', middleware.auth, require('./user'));
|
||||
// API routes for working with hosts. All endpoints need to be have valid user.
|
||||
router.use('/host', middleware.auth, require('./host'));
|
||||
|
||||
router.use('/dns', middleware.auth, require('./dns'));
|
||||
|
||||
// API routes for working with hosts. All endpoints need to be have valid user.
|
||||
router.use('/cert', middleware.auth, require('./cert'));
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
'use strict';
|
||||
|
||||
const router = require('express').Router();
|
||||
const {DnsProvider, Domain} = require('../models').models;
|
||||
|
||||
const Model = DnsProvider;
|
||||
|
||||
router.get('/', async function(req, res, next){
|
||||
try{
|
||||
return res.json({
|
||||
results: await Model[req.query.detail ? "listDetail" : "list"]()
|
||||
});
|
||||
}catch(error){
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.options('/', async function(req, res, next){
|
||||
try{
|
||||
return res.json({
|
||||
results: await Model.listProviders()
|
||||
});
|
||||
}catch(error){
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', async function(req, res, next){
|
||||
try{
|
||||
req.body.created_by = req.user.username;
|
||||
let item = await Model.create(req.body);
|
||||
|
||||
return res.json({
|
||||
message: `"${item[Model._key]}" added.`,
|
||||
...item,
|
||||
});
|
||||
} catch (error){
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/domain', async function(req, res, next){
|
||||
try{
|
||||
return res.json({
|
||||
results: await Domain[req.query.detail ? "listDetail" : "list"]()
|
||||
});
|
||||
}catch(error){
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/domain/refresh/:item', async function(req, res, next){
|
||||
try{
|
||||
let item = await Model.get(req.params.item);
|
||||
return res.json({results: await item.updateDomains()});
|
||||
}catch(error){
|
||||
next(error);
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/domain/:item', async function(req, res, next){
|
||||
try{
|
||||
return res.json({
|
||||
results: [await Domain.get(req.params.item)]
|
||||
});
|
||||
}catch(error){
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/:item', async function(req, res, next){
|
||||
try{
|
||||
|
||||
return res.json({
|
||||
item: req.params.item,
|
||||
results: await Model.get(req.params.item)
|
||||
});
|
||||
}catch(error){
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:item', async function(req, res, next){
|
||||
try{
|
||||
req.body.updated_by = req.user.username;
|
||||
let item = await Model.get(req.params.item);
|
||||
item = await item.update(req.body);
|
||||
|
||||
return res.json({
|
||||
message: `"${req.params.item}" updated.`,
|
||||
__requestedHost: req.params.item,
|
||||
...item,
|
||||
});
|
||||
|
||||
}catch(error){
|
||||
return next(error);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/:item', async function(req, res, next){
|
||||
try{
|
||||
let item = await Model.get(req.params.item);
|
||||
let count = await item.remove();
|
||||
|
||||
return res.json({
|
||||
message: `${req.params.item} deleted`,
|
||||
...item,
|
||||
});
|
||||
|
||||
}catch(error){
|
||||
return next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,14 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const router = require('express').Router();
|
||||
const {Host} = require('../models/host');
|
||||
const {Host, Domain} = require('../models').models;
|
||||
|
||||
const Model = Host;
|
||||
|
||||
router.get('/', async function(req, res, next){
|
||||
try{
|
||||
return res.json({
|
||||
hosts: await Model[req.query.detail ? "listDetail" : "list"]()
|
||||
results: await Model[req.query.detail ? "listDetail" : "list"](),
|
||||
});
|
||||
}catch(error){
|
||||
return next(error);
|
||||
|
||||
@@ -6,12 +6,13 @@ const router = require('express').Router();
|
||||
const conf = require('../conf');
|
||||
|
||||
const values ={
|
||||
title: conf.environment !== 'production' ? `<i class="fa-brands fa-dev"></i>` : ''
|
||||
title: conf.environment !== 'production' ? `dev` : '',
|
||||
titleIcon: conf.environment !== 'production' ? `<i class="fa-brands fa-dev"></i>` : '',
|
||||
}
|
||||
|
||||
// List of front end node modules to be served
|
||||
const frontEndModules = ['bootstrap', 'mustache', 'jquery', '@fortawesome',
|
||||
'moment',
|
||||
'moment', '@popper',
|
||||
];
|
||||
|
||||
// Server front end modules
|
||||
@@ -32,6 +33,11 @@ router.get('/hosts', async function(req, res, next) {
|
||||
res.render('hosts', {...values});
|
||||
});
|
||||
|
||||
router.get('/dns', async function(req, res, next) {
|
||||
res.render('dns', {...values});
|
||||
});
|
||||
|
||||
|
||||
router.get('/users', async function(req, res, next) {
|
||||
res.render('users', {...values});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const router = require('express').Router();
|
||||
const {User} = require('../models/user');
|
||||
const {User} = require('../models').models;
|
||||
|
||||
router.get('/', async function(req, res, next){
|
||||
try{
|
||||
|
||||
@@ -22,10 +22,8 @@ function ModelPs(model){
|
||||
}
|
||||
|
||||
return new Proxy(model, {
|
||||
construct(target, args) {
|
||||
let instance = ModelPs(new model(...args));
|
||||
|
||||
return instance;
|
||||
construct(target, args, newTarget) {
|
||||
return ModelPs(Reflect.construct(target, args, newTarget))
|
||||
},
|
||||
get(target, propKey, receiver) {
|
||||
if(propKey == 'constructor') return target.constructor;
|
||||
@@ -39,14 +37,15 @@ function ModelPs(model){
|
||||
res.then(function(res){
|
||||
publish(propKey, res, ...args);
|
||||
}).catch(function(error){
|
||||
console.log('toDo, publish errors...')
|
||||
|
||||
console.log('toDo, publish errors...');
|
||||
});
|
||||
}else{
|
||||
publish(propKey, res, ...args)
|
||||
publish(propKey, res, ...args);
|
||||
}
|
||||
return res;
|
||||
}catch(error){
|
||||
console.log("grrrr")
|
||||
console.log("toDo, publish errors...");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -21,22 +21,36 @@ function processKeys(map, data, partial){
|
||||
|
||||
for(let key of Object.keys(map)){
|
||||
|
||||
// Do not require "isRequired" fields for partial validation, useful for
|
||||
// updates.
|
||||
if(!map[key].always && partial && !data.hasOwnProperty(key)) continue;
|
||||
|
||||
// Make sure required keys are present
|
||||
if(!partial && map[key].isRequired && !data.hasOwnProperty(key)){
|
||||
errors.push({key, message:`${key} is required.`});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove undefined keys unless they have a default option or are a
|
||||
// relation
|
||||
if(data[key] === undefined){
|
||||
if(!map[key].default){
|
||||
if(map[key].model && !map[key].type) continue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the type of the key
|
||||
if(data.hasOwnProperty(key) && map[key].type && typeof(data[key]) !== map[key].type){
|
||||
errors.push({key, message:`${key} is not ${map[key].type} type.`});
|
||||
continue;
|
||||
}
|
||||
|
||||
// console.log(key, data[key], map[key].default, data.hasOwnProperty(key) && data[key] !== undefined ? data[key] : returnOrCall(map[key].default))
|
||||
|
||||
// Add the key to the process object to be returned and set any default
|
||||
// if the key is blank
|
||||
out[key] = data.hasOwnProperty(key) && data[key] !== undefined ? data[key] : returnOrCall(map[key].default);
|
||||
|
||||
// Check for type specific validations, ie: string length
|
||||
if(data.hasOwnProperty(key) && process_type[map[key].type]){
|
||||
let typeError = process_type[map[key].type](map[key], data[key]);
|
||||
if(typeError){
|
||||
@@ -47,8 +61,9 @@ function processKeys(map, data, partial){
|
||||
}
|
||||
}
|
||||
|
||||
// Check for errors, throw validation error if any
|
||||
if(errors.length !== 0){
|
||||
throw new ObjectValidateError(errors);
|
||||
throw ObjectValidateError(errors);
|
||||
return {__errors__: errors};
|
||||
}
|
||||
|
||||
@@ -56,6 +71,7 @@ function processKeys(map, data, partial){
|
||||
}
|
||||
|
||||
function parseFromString(map, data){
|
||||
// Use the key maps data type to return string values to native
|
||||
let types = {
|
||||
boolean: function(value){ return value === 'false' ? false : true },
|
||||
number: Number,
|
||||
@@ -80,11 +96,14 @@ function parseToString(data){
|
||||
return (types[typeof(data)] || String)(data);
|
||||
}
|
||||
|
||||
function ObjectValidateError(message){
|
||||
this.name = 'ObjectValidateError';
|
||||
this.message = (message || {});
|
||||
this.keys = (message || {})
|
||||
this.status = 422;
|
||||
function ObjectValidateError(keys, message){
|
||||
let error = new Error('ObjectValidateError')
|
||||
error.name = "ObjectValidateError"
|
||||
error.message = message || `Invalid Keys: ${message}`
|
||||
error.keys = (keys || {});
|
||||
error.status = 422;
|
||||
|
||||
return error
|
||||
}
|
||||
|
||||
ObjectValidateError.prototype = Error.prototype;
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
class PorkBun{
|
||||
baseUrl = 'https://api.porkbun.com/api/json/v3';
|
||||
|
||||
constructor(apiKey, secretApiKey){
|
||||
this.apiKey = apiKey;
|
||||
this.secretApiKey = secretApiKey;
|
||||
}
|
||||
|
||||
async post(url, data){
|
||||
let res;
|
||||
try{
|
||||
data = {
|
||||
...(data || {}),
|
||||
secretapikey: this.secretApiKey,
|
||||
apikey: this.apiKey,
|
||||
};
|
||||
res = await axios.post(`${this.baseUrl}${url}`, data);
|
||||
|
||||
return res;
|
||||
}catch(error){
|
||||
throw new Error(`PorkPun API ${error.response.status}: ${error.response.data.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
__typeCheck(type){
|
||||
if(!['A', 'MX', 'CNAME', 'ALIAS', 'TXT', 'NS', 'AAAA', 'SRV', 'TLSA', 'CAA', 'HTTPS', 'SVCB'].includes(type)) throw new Error('PorkBun API: Invalid type passed')
|
||||
}
|
||||
|
||||
__parseName(domain, name){
|
||||
if(name && !name.endsWith('.'+domain)){
|
||||
return `${name}.${domain}`
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
async getRecords(domain, options){
|
||||
let res = await this.post(`/dns/retrieve/${domain}`);
|
||||
if(!options) return res.data.records;
|
||||
|
||||
if(options.type) this.__typeCheck(options.type);
|
||||
if(options.name) options.name = this.__parseName(domain, options.name);
|
||||
let records = [];
|
||||
|
||||
for(let record of res.data.records){
|
||||
let matchCount = 0
|
||||
for(let option in options){
|
||||
if(record[option] === options[option] && ++matchCount === Object.keys(options).length){
|
||||
records.push(record)
|
||||
}
|
||||
// console.log('option', option, options[option], record[option], matchCount)
|
||||
}
|
||||
}
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
async deleteRecordById(domain, id){
|
||||
let res = this.post(`/dns/delete/${domain}/${id}`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async deleteRecords(domain, options){
|
||||
let records = await this.getRecords(domain, options);
|
||||
// console.log('PorkBun.deleteRecords', records)
|
||||
for(let record of records){
|
||||
await this.deleteRecordById(domain, record.id)
|
||||
}
|
||||
}
|
||||
|
||||
async createRecord(domain, options){
|
||||
this.__typeCheck(options.type);
|
||||
if(!options.content) throw new Error('PorkBun API: `content` key is required for this action')
|
||||
// if(options.name) options.name = this.__parseName(domain, options.name);
|
||||
// console.log('PorkBun.createRecord to send:', domain, options)
|
||||
|
||||
let res = this.post(`/dns/create/${domain}`, options);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async createRecordForce(domain, options){
|
||||
let {content, ...removed} = options;
|
||||
// console.log('new options', removed)
|
||||
let records = await this.getRecords(domain, removed);
|
||||
// console.log('createRecordForce', records)
|
||||
if(records.length){
|
||||
// console.log('calling delete on', records[0].id)
|
||||
// process.exit(0)
|
||||
await this.deleteRecordById(domain, records[0].id)
|
||||
}
|
||||
return await this.createRecord(domain, options)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PorkBun;
|
||||
|
||||
|
||||
if(require.main === module){(async function(){try{
|
||||
const conf = require('../conf');
|
||||
// let porkBun = new PorkBun(conf.porkBun.apiKey, conf.porkBun.secretApiKey);
|
||||
|
||||
// console.log(await porkBun.deleteRecordById('holycore.quest', '415509355'))
|
||||
// console.log('IIFE', await porkBun.createRecordForce('holycore.quest', {type:'A', name: 'testapi', content: '127.0.0.5'}))
|
||||
// console.log('IIFE', await porkBun.getRecords('holycore.quest', {type:'A', name: 'testapi'}))
|
||||
}catch(error){
|
||||
console.log('IIFE Error:', error)
|
||||
}})()}
|
||||
@@ -5,14 +5,53 @@ const objValidate = require('../utils/object_validate');
|
||||
const conf = require('../conf');
|
||||
|
||||
const client = createClient({});
|
||||
client.connect()
|
||||
client.connect();
|
||||
|
||||
function redisPrefix(key){
|
||||
return `${conf.redis.prefix}${key}`;
|
||||
}
|
||||
|
||||
class QueryHelper{
|
||||
hisroty = []
|
||||
constructor(orgin){
|
||||
this.orgin = orgin
|
||||
this.hisroty.push(orgin.constructor.name);
|
||||
}
|
||||
|
||||
static isNotCycle(modleName, queryHelper){
|
||||
if(queryHelper instanceof this){
|
||||
if(queryHelper.hisroty.includes(modleName)){
|
||||
return true;
|
||||
}
|
||||
queryHelper.hisroty.push(modleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Table{
|
||||
static errors = {
|
||||
ObjectValidateError: objValidate.ObjectValidateError,
|
||||
EntryNameUsed: ()=>{
|
||||
let error = new Error('EntryNameUsed');
|
||||
error.name = 'EntryNameUsed';
|
||||
error.message = `${this.prototype.constructor.name}:${data[this._key]} already exists`;
|
||||
error.keys = [{
|
||||
key: this._key,
|
||||
message: `${this.prototype.constructor.name}:${data[this._key]} already exists`
|
||||
}]
|
||||
error.status = 409;
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
static redisClient = client;
|
||||
|
||||
static models = {}
|
||||
static register = function(Model){
|
||||
Model = Model || this;
|
||||
this.models[Model.name] = Model;
|
||||
}
|
||||
|
||||
constructor(data){
|
||||
for(let key in data){
|
||||
@@ -20,7 +59,7 @@ class Table{
|
||||
}
|
||||
}
|
||||
|
||||
static async get(index){
|
||||
static async get(index, queryHelper){
|
||||
try{
|
||||
if(typeof index === 'object'){
|
||||
index = index[this._key];
|
||||
@@ -42,13 +81,37 @@ class Table{
|
||||
// back to native values.
|
||||
result = objValidate.parseFromString(this._keyMap, result);
|
||||
|
||||
return new this(result);
|
||||
|
||||
let instance = new this(result);
|
||||
await instance.buildRelations(queryHelper);
|
||||
|
||||
return instance;
|
||||
}catch(error){
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async buildRelations(queryHelper){
|
||||
|
||||
for(let [key, options] of Object.entries(this.constructor._keyMap)){
|
||||
if(options.model){
|
||||
let remoteModel = this.constructor.models[options.model]
|
||||
try{
|
||||
if(QueryHelper.isNotCycle(remoteModel.name, queryHelper)) continue;
|
||||
if(options.rel === 'one'){
|
||||
// console.log('relone:', this[key], queryHelper, remoteModel, await remoteModel.get(this[key], queryHelper || new QueryHelper(this)))
|
||||
this[key] = await remoteModel.get(this[key] || this[options.localKey || this.constructor._key] , queryHelper || new QueryHelper(this))
|
||||
}
|
||||
if(options.rel === 'many'){
|
||||
this[key] = await remoteModel.listDetail({
|
||||
[options.remoteKey]: this[options.localKey || this.constructor._key],
|
||||
},queryHelper || new QueryHelper(this))
|
||||
|
||||
}
|
||||
}catch{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async exists(index){
|
||||
if(typeof index === 'object'){
|
||||
index = index[this._key];
|
||||
@@ -72,17 +135,30 @@ class Table{
|
||||
}
|
||||
}
|
||||
|
||||
static async listDetail(){
|
||||
static async listDetail(options, queryHelper){
|
||||
|
||||
// Return a list of the entries as instances.
|
||||
let out = [];
|
||||
|
||||
for(let entry of await this.list()){
|
||||
out.push(await this.get(entry));
|
||||
let instance = await this.get(entry, arguments[arguments.length - 1]);
|
||||
if(!options) out.push(instance);
|
||||
let matchCount = 0;
|
||||
for(let option in options){
|
||||
if(instance[option] === options[option] && ++matchCount === Object.keys(options).length){
|
||||
out.push(instance);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static findall(...args){
|
||||
return this.listDetail(...args);
|
||||
}
|
||||
|
||||
static async create(data){
|
||||
// Add a entry to this redis table.
|
||||
try{
|
||||
@@ -210,6 +286,22 @@ class Table{
|
||||
}
|
||||
};
|
||||
|
||||
toJSON(){
|
||||
let result = {};
|
||||
for (const [key, value] of Object.entries(this)) {
|
||||
if(this.constructor._keyMap[key] && this.constructor._keyMap[key].isPrivate) continue;
|
||||
result[key] = value;
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
// return JSON.stringify(result);
|
||||
}
|
||||
|
||||
toString(){
|
||||
return this[this.constructor._key];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
<%- include('top') %>
|
||||
<script type="text/javascript">
|
||||
// Require login to see this page.
|
||||
app.auth.forceLogin();
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
label.form-label{
|
||||
font-weight: bold;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.card-title{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.form-group{
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
select {
|
||||
font-family: "system-ui", "FontAwesome";
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function providerGet(){
|
||||
app.api.options('dns', function(error, res){
|
||||
for(let provider of res.results){
|
||||
$.scope.providerSelect.push(provider);
|
||||
|
||||
for(let field in provider.fields){
|
||||
$.scope.providerField.push({
|
||||
...provider.fields[field],
|
||||
keyName: field,
|
||||
provider: provider.name
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function providerShowForm(){
|
||||
let $el = $(event.target);
|
||||
$('.jq-repeat-providerField').each(function(){
|
||||
let $el = $(this)
|
||||
$el.hide()
|
||||
$el.find('input').prop('disabled', true)
|
||||
});
|
||||
|
||||
$(`.provider-form-${$el.val()}`).each(function(){
|
||||
let $el = $(this)
|
||||
$el.show()
|
||||
$el.find('input').prop("disabled",false)
|
||||
});
|
||||
}
|
||||
|
||||
$.scope.DnsProvider.parseData = function(row){
|
||||
row['created_on_text'] = moment(row['updated_on'], "x").fromNow();
|
||||
row['domainsString'] = JSON.stringify(row.domains);
|
||||
return row
|
||||
};
|
||||
|
||||
$(document).ready(async function(){
|
||||
// Set the jq Templates
|
||||
$.scope.providerField.put = function(){};
|
||||
$.scope.DnsProvider.push(...(await app.api.get('/dns?detail=true')).results);
|
||||
|
||||
// Populate
|
||||
providerGet();
|
||||
|
||||
app.subscribe(/^model:/, function(data, topic){
|
||||
let [group, Model, action, pk] = topic.split(':');
|
||||
console.log('WS:', group, Model, action, pk, data);
|
||||
});
|
||||
|
||||
app.subscribe(/^model:.+:create/, function(data, topic){
|
||||
try{
|
||||
let [group, Model, action, pk] = topic.split(':');
|
||||
$.scope[Model].unshift(data)
|
||||
}catch{}
|
||||
});
|
||||
|
||||
app.subscribe(/^model:.+:remove/, function(data, topic){
|
||||
try{
|
||||
let [group, Model, action, pk] = topic.split(':');
|
||||
$.scope[Model].remove(pk);
|
||||
}catch{}
|
||||
});
|
||||
|
||||
app.subscribe(/^model:.+:update/, function(data, topic){
|
||||
try{
|
||||
let [group, Model, action, pk] = topic.split(':');
|
||||
$.scope[Model].update(pk, data)
|
||||
}catch{}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
<div class="row mb-3" style="display:none">
|
||||
<div class="col-md-3">
|
||||
<div class="card shadow-lg mb-3">
|
||||
|
||||
<div class="card-header text-center">
|
||||
<span class="card-icon float-start">
|
||||
<i class="fa-solid fa-user-plus"></i>
|
||||
</span>
|
||||
<span class="card-title">
|
||||
Add DNS Provider
|
||||
</span>
|
||||
<span class="float-end">
|
||||
<i class="fa-solid fa-circle-minus"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="card-header actionMessage" style="display:none"></div>
|
||||
<div class="card-body d-none d-md-block">
|
||||
<form action="dns/" onsubmit="formAJAX(this)">
|
||||
<div class="form-group">
|
||||
<label for="name" class="form-label">
|
||||
Name
|
||||
</label>
|
||||
<input type="text" name="name" class="form-control" placeholder="ex: production" validate=":3"/>
|
||||
<b class="invalid-feedback"></b>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dnsProvider" class="form-label">
|
||||
DNS provider
|
||||
</label>
|
||||
<select name="dnsProvider" class="form-select" aria-label="Default select example" validate=":1" oninput="providerShowForm()">
|
||||
<option value="" selected>Select a provider</option>
|
||||
<option jq-repeat="providerSelect" value="{{name}}">{{{displayIconUni}}} {{name}}</option>
|
||||
|
||||
</select>
|
||||
<b class="invalid-feedback"></b>
|
||||
</div>
|
||||
|
||||
<div class="form-group provider-form-{{ provider }}" jq-repeat="providerField" style="display:none;">
|
||||
<label for="{{ keyName }}" class="form-label">
|
||||
{{displayName}}
|
||||
</label>
|
||||
<input type="text" name="{{ keyName }}" class="form-control" validate=":3" disabled/>
|
||||
<b class="invalid-feedback"></b>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
Add
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="row row-cols-1 row-cols-md-2 g-4">
|
||||
|
||||
<div jq-repeat="DnsProvider" jq-repeat-index="id" style="display:none" class="col">
|
||||
<div class="card shadow-lg">
|
||||
|
||||
<div class="card-header text-center">
|
||||
<span class="card-icon float-start">
|
||||
<i class="fa-solid fa-record-vinyl"></i>
|
||||
</span>
|
||||
<span class="card-title">
|
||||
{{ dnsProvider }} DNS
|
||||
</span>
|
||||
<span class="float-end">
|
||||
<i class="fa-solid fa-circle-minus"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="card-header actionMessage" style="display:none"></div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<h3><img height="32px" src="{{ displayIconHtml }}"/> {{name}} </h3>
|
||||
</div>
|
||||
<div>
|
||||
<b jq-repeat='Domain_{{id}}' jq-repeat-index="domain" class="me-2 my-1 badge text-bg-info rounded-pill fs-6">
|
||||
{{domain}}
|
||||
</b>
|
||||
<script type="text/javascript">
|
||||
try{
|
||||
$.scope.Domain_{{id}}.push(...{{{domainsString}}});
|
||||
}catch{}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
Added <b class="momentFromNow" data-date="{{ updated_on }}">{{ created_on_text }}</b> by <b>{{ created_by }}</b>
|
||||
<span class="float-end text-end">
|
||||
<button type="button" class="btn btn-warning" method="POST" action="/dns/domain/refresh/{{id}}" onclick="formAJAX()">
|
||||
<i class="fa-solid fa-rotate"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" method="DELETE" action="dns/{{id}}" onclick="formAJAX()">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<%- include('bottom') %>
|
||||
+113
-109
@@ -10,13 +10,6 @@
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.card-title{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.hostEditPanel{
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
div.form-group{
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
@@ -62,19 +55,19 @@
|
||||
}
|
||||
|
||||
function hostPopulate(){
|
||||
app.host.list(function(error, res){
|
||||
app.api.get('host?detail=1&provider=1', function(error, res){
|
||||
if(error) return app.util.actionMessage(error, $.scope.hosts.$this, 'danger');
|
||||
|
||||
for(let host of res){
|
||||
for(let host of res.results){
|
||||
$.scope.hosts.push(hostParseRow(host));
|
||||
}
|
||||
|
||||
$.scope.hosts.__setPut(function($el, item, list){
|
||||
$.scope.hosts.put = function($el, item, list){
|
||||
$el.addClass('table-success');
|
||||
$el.fadeIn(2000, function(){
|
||||
$el.removeClass('table-success');
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,14 +82,21 @@
|
||||
hostEditCancle();
|
||||
host = $.scope.hosts.getByKey(host);
|
||||
host.__jq_$el.addClass('table-warning');
|
||||
$editHostForm.find('[name=is_wildcard').attr('disabled', true);
|
||||
$.scope.editHost.update({...host, form: $editHostForm.html()});
|
||||
|
||||
if(host.is_wildcard){
|
||||
$('.hostEditPanel [name="host"]').attr('disabled', true);
|
||||
}
|
||||
|
||||
$.each(host, function( key, value ) { if(typeof value == "boolean"){
|
||||
$(".hostEditPanel #"+ key +"-"+ value).prop('checked', true)
|
||||
}else{
|
||||
$(".hostEditPanel input[name='" + key + "']").val(value);
|
||||
}
|
||||
});
|
||||
|
||||
$('.hostEditPanel').scrollTo();
|
||||
};
|
||||
|
||||
function hostDownloadCert(host, type){
|
||||
@@ -127,32 +127,31 @@
|
||||
$editHostForm = $('#addHost').clone();
|
||||
$editHostForm.find('hr.buttonBreak').nextAll().remove();
|
||||
// $editHostForm.find('.autoSll').addClass('bg-secondary');
|
||||
$editHostForm.find('[name=is_wildcard').attr('disabled', true);
|
||||
hostPopulate(); //populate the table
|
||||
|
||||
$.scope.hosts.__setTake(function($el, item, list){
|
||||
$.scope.hosts.take = function($el, item, list){
|
||||
$el.addClass('table-danger');
|
||||
$el.fadeOut(1000, function(){
|
||||
$el.remove()
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$.scope.hosts.__setUpdate(function($el, $render, item, list){
|
||||
$.scope.hosts.putUpdate = function($el, $render, item, list){
|
||||
$render.show()
|
||||
$el.replaceWith($render);
|
||||
});
|
||||
};
|
||||
|
||||
$.scope.editHost.__setPut(function($el, item, list){
|
||||
$.scope.editHost.put = function($el, item, list){
|
||||
$el.slideDown();
|
||||
});
|
||||
};
|
||||
|
||||
$.scope.editHost.__setTake(function($el, item, list){
|
||||
$.scope.editHost.take = function($el, item, list){
|
||||
$el.slideUp();
|
||||
});
|
||||
};
|
||||
|
||||
// app.subscribe(/^model:Host/, function(data, topic){
|
||||
// console.log(topic, data);
|
||||
// });
|
||||
app.subscribe(/^model:Host/, function(data, topic){
|
||||
console.log(topic, data);
|
||||
});
|
||||
|
||||
app.subscribe(/^model:Host:create/, function(data, topic){
|
||||
let [a,b, action, host] = topic.split(':');
|
||||
@@ -187,7 +186,7 @@
|
||||
<!--
|
||||
left column
|
||||
-->
|
||||
<div jq-repeat="editHost" class="card shadow-lg border-warning hostEditPanel" style="display:none">
|
||||
<div jq-repeat="editHost" class="card shadow-lg border-warning hostEditPanel mb-3" style="display:none">
|
||||
<!--
|
||||
Edit host card
|
||||
-->
|
||||
@@ -220,7 +219,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-lg hostAddPanel">
|
||||
<div class="card shadow-lg mb-3 hostAddPanel">
|
||||
<!--
|
||||
Add new host card
|
||||
-->
|
||||
@@ -238,7 +237,7 @@
|
||||
|
||||
<div class="card-header actionMessage" style="display:none"></div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="card-body d-none d-md-block">
|
||||
<form class="addHost" id="addHost" method="POST" action="host" onsubmit="formAJAX(this)">
|
||||
|
||||
<div class="form-group">
|
||||
@@ -357,94 +356,99 @@
|
||||
<label class="form-label" for="search" style="margin-left: -5px;">
|
||||
Search Hosts:
|
||||
</label>
|
||||
<input type="search" oninput="hostSearchInput()">
|
||||
<input type="search" oninput="hostSearchInput()" />
|
||||
</div>
|
||||
<table class="card-body table table-striped" style="margin-bottom:0">
|
||||
|
||||
<thead>
|
||||
<th>
|
||||
SSL Expire
|
||||
</th>
|
||||
<th>
|
||||
Host Name
|
||||
</th>
|
||||
<th>
|
||||
target
|
||||
</th>
|
||||
<th class="hidden-xs">
|
||||
Updated
|
||||
</th>
|
||||
<th>
|
||||
Actions
|
||||
</th>
|
||||
</thead>
|
||||
<div class='table-responsive'>
|
||||
<table class="m-0 card-body table table-striped overflow-x-scroll">
|
||||
|
||||
<thead>
|
||||
<th>
|
||||
SSL Expire
|
||||
</th>
|
||||
<th>
|
||||
Host Name
|
||||
</th>
|
||||
<th>
|
||||
target
|
||||
</th>
|
||||
<th class="hidden-xs">
|
||||
Updated
|
||||
</th>
|
||||
<th>
|
||||
Actions
|
||||
</th>
|
||||
</thead>
|
||||
|
||||
<tbody class="card-body" id="hostsTable">
|
||||
<tr action="api" jq-repeat="hosts" jq-repeat-index='host' style="display:none">
|
||||
<td class="table-{{wildcard_text_bg}}">
|
||||
{{{ wildcard_text }}}
|
||||
{{#wildcard_expires}}
|
||||
<span class="momentFromNow" data-date="{{ wildcard_expires }}" >{{wildcard_expires_text}}</span>
|
||||
{{/wildcard_expires}}
|
||||
</td>
|
||||
<td>
|
||||
<a target="_blank" href="{{ forcessl_text }}{{ host }}">
|
||||
{{{ forcessl_text }}}{{ host }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{{ targetssl_text }}}{{ ip }}:{{ targetPort }}
|
||||
</td>
|
||||
<td class="hidden-xs momentFromNow" data-date="{{ updated_on }}" >
|
||||
{{ updated_on_text }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<tbody>
|
||||
<tr action="api" jq-repeat="hosts" jq-repeat-index='host' style="display:none">
|
||||
<td class="table-{{wildcard_text_bg}}">
|
||||
{{{ wildcard_text }}}
|
||||
{{#wildcard_expires}}
|
||||
<span class="momentFromNow" data-date="{{ wildcard_expires }}" >{{wildcard_expires_text}}</span>
|
||||
{{/wildcard_expires}}
|
||||
</td>
|
||||
<td>
|
||||
<a target="_blank" href="{{ forcessl_text }}{{ host }}">
|
||||
{{{ forcessl_text }}}{{ host }}
|
||||
</a>
|
||||
{{#domain.provider}}
|
||||
<br />
|
||||
<img width="24px" src="{{displayIconHtml}}" /> {{displayName}} - {{name}}
|
||||
{{/domain.provider}}
|
||||
</td>
|
||||
<td>
|
||||
{{{ targetssl_text }}}{{ ip }}:{{ targetPort }}
|
||||
</td>
|
||||
<td class="hidden-xs momentFromNow" data-date="{{ updated_on }}" >
|
||||
{{ updated_on_text }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-brands fa-expeditedssl"></i>
|
||||
Certs
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-brands fa-expeditedssl"></i>
|
||||
Certs
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<button type="button" class="dropdown-item" onclick="hostDownloadCert('{{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="hostDownloadCert('{{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="hostDownloadCert('{{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="hostEditOpen(this, '{{ host }}');" class="btn btn-sm btn-warning">
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" method="DELETE" action="host/{{host}}" onclick="formAJAX()" class="btn btn-sm btn-danger">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
Delete
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<button type="button" class="dropdown-item" onclick="hostDownloadCert('{{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="hostDownloadCert('{{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="hostDownloadCert('{{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="hostEditOpen(this, '{{ host }}');" class="btn btn-sm btn-warning">
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" method="DELETE" action="host/{{host}}" onclick="formAJAX()" class="btn btn-sm btn-danger">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+42
-40
@@ -1,8 +1,8 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>Proxy - Theta 42 <%- title %></title>
|
||||
<!-- CSS are placed here -->
|
||||
<link rel="stylesheet" href="/static-modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
@@ -13,7 +13,7 @@
|
||||
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
|
||||
<script type="text/javascript" src='/static-modules/jquery/dist/jquery.js'></script>
|
||||
<!-- <script type="text/javascript" src="/static/lib/js/popper-1.16.0.min.js"></script> -->
|
||||
<script type="text/javascript" src="/static-modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<!-- <script type="text/javascript" src="/static-modules/bootstrap/dist/js/bootstrap.min.js"></script> -->
|
||||
<script type="text/javascript" src="/static-modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script type="text/javascript" src="/static-modules/@fortawesome/fontawesome-free/js/all.min.js"></script>
|
||||
<script type="text/javascript" src='/static-modules/mustache/mustache.min.js'></script>
|
||||
@@ -32,47 +32,49 @@
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
|
||||
<a class="navbar-brand" href="#">Dynamic Proxy <%- title %></a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse justify-content-end" id="navbarCollapse">
|
||||
<ul class="navbar-nav top-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/hosts"><i class="fa-solid fa-network-wired"></i> Hosts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/users"><i class="fa-solid fa-users"></i> Users Panel</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/topics"> Topics</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/theta42/proxy" target="_blank">
|
||||
<i class="fa-brands fa-github"></i>
|
||||
<a class="navbar-brand" href="#">Dynamic Proxy <%- titleIcon %></a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse justify-content-end" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav top-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/hosts">
|
||||
<i class="fa-solid fa-network-wired"></i>
|
||||
Hosts
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="form-inline mt-2 mt-md-0">
|
||||
<!-- Show the login/logout button -->
|
||||
<a id="cl-login-button" class="btn btn-outline-danger my-2 my-sm-0" onclick="app.auth.forceLogin()" style="display: none;">
|
||||
<i class="fas fa-sign-out"></i>
|
||||
Login
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/dns"><i class="fa-solid fa-record-vinyl"></i>
|
||||
DNS
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/users"><i class="fa-solid fa-users"></i>
|
||||
Users
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/theta42/proxy" target="_blank">
|
||||
<i class="fa-brands fa-github"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="form-inline mt-2 mt-md-0">
|
||||
<a id="cl-login-button" class="btn btn-outline-danger my-2 my-sm-0" onclick="app.auth.forceLogin()" style="display: none;">
|
||||
<i class="fas fa-sign-out"></i>
|
||||
Login
|
||||
</a>
|
||||
|
||||
<button id="cl-logout-button" class="btn btn-outline-danger my-2 my-sm-0" onclick="app.auth.logOut(e => window.location.href='/')" style="display: none;">
|
||||
<i class="fas fa-sign-out"></i>
|
||||
Log Out
|
||||
</button>
|
||||
|
||||
<!-- <input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
|
||||
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> -->
|
||||
</div>
|
||||
</div>
|
||||
<button id="cl-logout-button" class="btn btn-outline-danger my-2 my-sm-0" onclick="app.auth.logOut(e => window.location.href='/')" style="display: none;">
|
||||
<i class="fas fa-sign-out"></i>
|
||||
Log Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
// window.location.pathname.toLocaleLowerCase()
|
||||
|
||||
// Set the correct link to active in the top nav bar
|
||||
$('.top-nav a').each(function(index){
|
||||
|
||||
Generated
+3
-488
@@ -1,491 +1,6 @@
|
||||
{
|
||||
"name": "proxy",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
||||
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
||||
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
|
||||
"requires": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^2.0.6"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"bcrypt": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-4.0.1.tgz",
|
||||
"integrity": "sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ==",
|
||||
"requires": {
|
||||
"node-addon-api": "^2.0.0",
|
||||
"node-pre-gyp": "0.14.0"
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
|
||||
},
|
||||
"delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
|
||||
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
|
||||
"requires": {
|
||||
"minipass": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
||||
"requires": {
|
||||
"aproba": "^1.0.3",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^1.0.1",
|
||||
"strip-ansi": "^3.0.1",
|
||||
"wide-align": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ignore-walk": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
|
||||
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
|
||||
"requires": {
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
|
||||
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
|
||||
"requires": {
|
||||
"minipass": "^2.9.0"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz",
|
||||
"integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==",
|
||||
"requires": {
|
||||
"debug": "^3.2.6",
|
||||
"iconv-lite": "^0.4.4",
|
||||
"sax": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz",
|
||||
"integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA=="
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz",
|
||||
"integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==",
|
||||
"requires": {
|
||||
"detect-libc": "^1.0.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"needle": "^2.2.1",
|
||||
"nopt": "^4.0.1",
|
||||
"npm-packlist": "^1.1.6",
|
||||
"npmlog": "^4.0.2",
|
||||
"rc": "^1.2.7",
|
||||
"rimraf": "^2.6.1",
|
||||
"semver": "^5.3.0",
|
||||
"tar": "^4.4.2"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
||||
"requires": {
|
||||
"abbrev": "1",
|
||||
"osenv": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"npm-bundled": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
|
||||
"integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
|
||||
"requires": {
|
||||
"npm-normalize-package-bin": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"npm-normalize-package-bin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
|
||||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
|
||||
},
|
||||
"npm-packlist": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
|
||||
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
|
||||
"requires": {
|
||||
"ignore-walk": "^3.0.1",
|
||||
"npm-bundled": "^1.0.1",
|
||||
"npm-normalize-package-bin": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
||||
"requires": {
|
||||
"are-we-there-yet": "~1.1.2",
|
||||
"console-control-strings": "~1.1.0",
|
||||
"gauge": "~2.7.3",
|
||||
"set-blocking": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
||||
},
|
||||
"osenv": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
|
||||
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
|
||||
"requires": {
|
||||
"os-homedir": "^1.0.0",
|
||||
"os-tmpdir": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"requires": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
"strip-ansi": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.13",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
|
||||
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"fs-minipass": "^1.2.5",
|
||||
"minipass": "^2.8.6",
|
||||
"minizlib": "^1.2.1",
|
||||
"mkdirp": "^0.5.0",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
||||
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
||||
"requires": {
|
||||
"string-width": "^1.0.2 || 2"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||
}
|
||||
}
|
||||
"packages": {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user