fixed issue with redis models

This commit is contained in:
2024-08-06 13:11:55 -04:00
parent e38504457c
commit 84db245f4a
20 changed files with 558 additions and 323 deletions
+20 -12
View File
@@ -53,20 +53,28 @@ app.use('/api', require('./routes/api'));
// Catch 404 and forward to error handler. If none of the above routes are
// used, this is what will be called.
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.message = 'Page not found'
err.status = 404;
next(err);
app.use(async function(req, res, next) {
try{
var err = new Error('Not Found');
err.message = 'Page not found'
err.status = 404;
next(err);
}catch(error){
console.log('app 404 catch error', error)
}
});
// Error handler. This is where `next()` will go on error
app.use(function(err, req, res, next) {
console.error(err.status || res.status, err.name, req.method, req.url);
console.error(err.message);
console.error(err.stack);
console.error('=========================================');
app.use(async function(err, req, res, next) {
try{
console.error(err.status || res.status, err.name, req.method, req.url);
console.error(err.message);
console.error(err.stack);
console.error('=========================================');
res.status(err.status || 500);
res.json({name: err.name, message: err.message});
res.status(err.status || 500);
res.json({name: err.name, message: err.message, keys: err.keys});
}catch(error){
console.log('error in the catch all error fn....', error);
}
});
-8
View File
@@ -45,12 +45,4 @@ class Auth{
}
}
Auth.logOut = async function(data){
try{
}catch(error){
throw error;
}
}
module.exports = {Auth};
+101 -27
View File
@@ -28,6 +28,7 @@ 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_parent': {isRequired: false, type: 'string', min: 3, max: 500},
}
@@ -84,11 +85,11 @@ class Host extends Table{
}
}
static async add(data, ...args){
static async create(data, ...args){
try{
let out = await super.create(data, ...args)
await this.buildLookUpObj()
if(out.is_wildcard) await out.createWildcardCert()
let out = await super.create(data, ...args);
await this.buildLookUpObj();
if(out.is_wildcard) out.createWildcardCert();
return out;
@@ -100,31 +101,104 @@ class Host extends Table{
async createWildcardCert(){
if(!this.host.startsWith('*.')) throw new Error('not wild card');
let cert = await letsEncrypt.dnsWildcard(this.host, {
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
let parts = tldExtract(authz.identifier.value);
let res = await porkBun.createRecordForce(parts.domain, {type:'TXT', name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`, content: `${keyAuthorization}`});
},
challengeRemoveFn: async (authz, challenge, keyAuthorization)=>{
let parts = tldExtract(authz.identifier.value);
await porkBun.deleteRecords(parts.domain, {type:'TXT', name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`, content: `${keyAuthorization}`});
},
});
try{
let host = this;
let cert = await letsEncrypt.dnsWildcard(this.host, {
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
host.update({
wildcard_status: `Adding record for ${authz.identifier.value}`
});
try{
let parts = tldExtract(authz.identifier.value);
let toAdd = {
cert_pem: cert.cert.split('\n\n')[0],
fullchain_pem: cert.cert,
privkey_pem: cert.key.toString(),
csr_pem: cert.csr.toString(),
expiry: 4120307657,
real_expiry: +LetsEncrypt.AcmeClient.crypto.readCertificateInfo(cert.cert).notAfter/1000,
console.log('adding DNS record for', `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`)
let res = await porkBun.createRecordForce(
parts.domain,
{
type:'TXT',
name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`,
content: `${keyAuthorization}`
}
);
console.log('porkbun res', res)
}catch(error){
console.log('model Host challengeCreateFn error:', error)
host.update({
wildcard_status: `Add DNS record failed`
});
}
},
onDnsCheck: async(authz, checkCount)=>{
host.update({
wildcard_status: `${checkCount} Checking DNS for ${authz.identifier.value}`
});
},
onDnsCheckFail: async(authz, error)=>{
host.update({
wildcard_status: `DNS check failed for ${authz.identifier.value}`
});
},
onDnsCheckFound: async(authz)=>{
host.update({
wildcard_status: `DNS check found for ${authz.identifier.value}`
});
},
onDnsCheckSuccess: async(authz)=>{
host.update({
wildcard_status: `DNS check success for ${authz.identifier.value}`
})
},
onDnsCheckRemove: async(authz)=>{
host.update({
wildcard_status: `DNS remove record for ${authz.identifier.value}`
})
},
challengeRemoveFn: async (authz, challenge, keyAuthorization)=>{
host.update({
wildcard_status: `DNS remove record for ${authz.identifier.value}`
})
try{
// let parts = tldExtract(authz.identifier.value);
// await porkBun.deleteRecords(
// parts.domain,
// {
// type:'TXT',
// name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''
// }`,
// content: `${keyAuthorization}`}
// );
}catch(error){
host.update({
wildcard_status: `DNS remove record failed for ${authz.identifier.value}`
})
}
},
});
let toAdd = {
cert_pem: cert.cert.split('\n\n')[0],
fullchain_pem: cert.cert,
privkey_pem: cert.key.toString(),
csr_pem: cert.csr.toString(),
expiry: 4120307657,
real_expiry: +LetsEncrypt.AcmeClient.crypto.readCertificateInfo(cert.cert).notAfter/1000,
}
await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd));
this.update({
wildcard_status: `Done`
});
return this;
}catch(error){
console.log('le failed', error)
this.update({
wildcard_status: `LE failed`
});
}
await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd));
return toAdd;
// Get new wildcard cert from
}
async update(...args){
+1 -1
View File
@@ -37,7 +37,7 @@ class AuthToken extends Token{
return await User.get(this.created_by);
}
static async add(data){
static async create(data){
data.created_by = data.username;
return super.create(data)
+5 -40
View File
@@ -1,7 +1,6 @@
'use strict';
const Table = require('../utils/redis_model');
// const {Token, InviteToken} = require('./token');
const bcrypt = require('bcrypt');
const saltRounds = 10;
@@ -19,9 +18,11 @@ class User extends Table{
static backing = 'redis'
static async add(data) {
static async create(data) {
try{
console.log('hash?')
data['password'] = await bcrypt.hash(data['password'], saltRounds);
console.log('hashed password', data.password);
data['backing'] = data['backing'] || 'redis';
@@ -32,31 +33,6 @@ class User extends Table{
}
}
/* static async addByInvite(data){
try{
let token = await InviteToken.get(data.token);
if(!token.is_valid){
let error = new Error('Token Invalid');
error.name = 'Token Invalid';
error.message = `Token is not valid or as allready been used. ${data.token}`;
error.status = 401;
throw error;
}
let user = await this.create(data);
if(user){
await token.consume({claimed_by: user.username});
return user;
}
}catch(error){
throw error;
}
};*/
async setPassword(data){
try{
data['password'] = await bcrypt.hash(data['password'], saltRounds);
@@ -67,17 +43,6 @@ class User extends Table{
}
}
/* async invite(){
try{
let token = await InviteToken.create({created_by: this.username});
return token;
}catch(error){
throw error;
}
}*/
static async login(data){
try{
@@ -109,7 +74,7 @@ module.exports = {User};
(async function(){
var defaultUser = 'proxyadmin3'
var defaultUser = 'proxyadmin2'
try{
let user = await User.get(defaultUser);
}catch(error){
@@ -117,7 +82,7 @@ module.exports = {User};
let user = await User.create({
username:defaultUser,
password: defaultUser,
created_by:defaultUser
created_by: defaultUser
});
console.log(defaultUser, 'created', user);
}catch(error){
+1 -1
View File
@@ -15,7 +15,7 @@
"bcrypt": "^5.1.1",
"bootstrap": "^5.3.3",
"ejs": "^3.1.10",
"express": "^4.19.2",
"express": "^4.18.2",
"extend": "^3.0.2",
"jquery": "^3.7.1",
"ldapts": "^2.12.0",
+1 -1
View File
@@ -18,7 +18,7 @@
"bcrypt": "^5.1.1",
"bootstrap": "^5.3.3",
"ejs": "^3.1.10",
"express": "^4.19.2",
"express": "^4.18.2",
"extend": "^3.0.2",
"jquery": "^3.7.1",
"ldapts": "^2.12.0",
+47 -25
View File
@@ -252,10 +252,10 @@ app.user = (function(app){
app.util = (function(app){
function getUrlParameter(name){
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};
function actionMessage(message, $target, type, callback){
@@ -277,28 +277,38 @@ app.util = (function(app){
})
}else{
if(type) $target.addClass('bg-' + type);
message = '<span class="align-middle">' + message + '</span><button class="action-close btn btn-sm btn-outline-dark float-right"><i class="fa-solid fa-xmark"></i></button>'
message = '<span class="align-middle">' + message + '</span><button class="action-close btn btn-sm btn-outline-dark float-end"><i class="fa-solid fa-xmark"></i></button>'
$target.html(message).slideDown('fast');
}
setTimeout(callback,10)
}
$.fn.serializeObject = function(){
var
arr = $(this).serializeArray(),
obj = {};
for(var i = 0; i < arr.length; i++){
if(obj[arr[i].name] === undefined) {
obj[arr[i].name] = arr[i].value;
} else {
if(!(obj[arr[i].name] instanceof Array)) {
obj[arr[i].name] = [obj[arr[i].name]];
}
obj[arr[i].name].push(arr[i].value);
}
}
return obj;
var
arr = $(this).serializeArray(),
obj = {};
for(var i = 0; i < arr.length; i++){
if(obj[arr[i].name] === undefined) {
if(!arr[i].value) continue;
obj[arr[i].name] = arr[i].value;
let type = $(this).parent().find(`[name="${arr[i].name}"]`).attr('type');
if(['number', 'range'].includes(type)){
obj[arr[i].name] = Number(arr[i].value);
}
if(['radio'].includes(type) && ['true', 'false'].includes(arr[i].value)){
obj[arr[i].name] = arr[i].value == 'true' ? true : false;
}
} else {
if(!(obj[arr[i].name] instanceof Array)) {
obj[arr[i].name] = [obj[arr[i].name]];
}
obj[arr[i].name].push(arr[i].value);
}
}
return obj;
};
return {
@@ -342,12 +352,12 @@ function formAJAX(btn, del){
event.preventDefault(); // avoid to execute the actual submit of the form.
var $form = $(btn).closest('[action]'); // gets the 'form' parent
var formData = $form.find('[name]').serializeObject(); // builds query formDataing
var method = $form.attr('method') || 'post';
var method = ($form.attr('method') || 'post').toLowerCase();
// if( !$form.validate()){
// app.util.actionMessage('Please fix the form errors.', $form, 'danger')
// return false;
// }
if($form.validate && !$form.validate()){
app.util.actionMessage('Please fix the form errors.', $form, 'danger')
return false;
}
app.util.actionMessage(
'<div class="spinner-border" role="status"><span class="sr-only">Loading...</span></div>',
@@ -357,9 +367,21 @@ function formAJAX(btn, del){
app.api[method]($form.attr('action'), formData, function(error, data){
app.util.actionMessage(data.message, $form, error ? 'danger' : 'success'); //re-populate table
$form.validateClear();
if(!error){
$form.trigger("reset");
eval($form.attr('evalAJAX')); //gets JS to run after completion
}else{
console.log('formAJAX res error', error, data)
if(data && data.name === 'ObjectValidateError'){
app.util.actionMessage('Please fix the form errors', $form, 'danger'); //re-populate table
}
if(data && data.keys){
console.log('form key errors', data.keys)
for(let keyError of data.keys){
$form.find(`[name=${keyError.key}]`).validateMessage(keyError.message);
}
}
}
});
}
+44 -21
View File
@@ -10,7 +10,7 @@ MIT license
$.scope = {};
}
var make = function( element ){
var make = function(element){
var result = [];
result.splice = function(inputValue, ...args){
@@ -20,9 +20,9 @@ MIT license
var index;
//if a string is submitted as the index, try to match it to index number
if( typeof arguments[0] === 'string' ){
if(typeof arguments[0] === 'string'){
index = this.indexOf( arguments[0] );//set where to start
if ( index === -1 ) {
if (index === -1) {
return [];
}
}else{
@@ -109,6 +109,7 @@ MIT license
//set and return new array
return Array.prototype.splice.apply(this, toProto);
};
result.push = function(){
//add one or more objects to the array
@@ -123,19 +124,25 @@ MIT license
//return new array length
return this.length;
};
result.pop = function(){
//remove and return array element
return this.splice( -1, 1 )[0];
};
result.reverse = function() {
var temp = this.splice( 0 );
Array.prototype.reverse.apply( temp );
for( var i = 0; i < temp.length; i++ ){
this.push( temp[i] );
result.reverse = function() {
let hold = [];
for(let item of this){
hold.push(item.__jq_$el.html())
}
for(let idx in hold.reverse()){
this[idx].__jq_$el.html(hold[idx]);
}
Array.prototype.reverse.apply( this );
return this;
};
@@ -149,6 +156,10 @@ MIT license
return this.splice( 0, 1 )[0];
};
result.unshift = function(data){
return this.splice(0,0, data)
}
result.loop = function(){
var temp = this[0];
this.splice( 0,1 );
@@ -156,13 +167,17 @@ MIT license
return temp;
};
result.loopUp = function(){
var temp = this[this.length-1];
this.splice( -1, 1 );
this.splice( 0, 0, temp );
return temp;
};
result.indexOf = function( key, value ){
if(typeof key === 'number') return key;
if( typeof value !== 'string' ){
value = arguments[0];
key = this.__index;
@@ -175,7 +190,8 @@ MIT license
}
return -1;
};
result.update = function( key, value, update ){
result.update = function(key, value, data){
//set variables using sting for index
// If update is called with no index/key, assume its the 0
@@ -186,24 +202,31 @@ MIT license
return this.splice(0, 1, key);
}
if( typeof value !== 'string' ){
update = arguments[1];
value = arguments[0];
key = this.__index;
if(typeof value !== 'string'){
data = arguments[1];
if(typeof key !== 'number'){
value = arguments[0];
key = this.__index;
}
}
var index = this.indexOf( key, value );
if(index === -1) {
return [];
}
var object = $.extend( true, {}, this[index], update );
var $render = $(Mustache.render(this.__jqTemplate, object));
$render.attr('jq-repeat-index', index);
this[index].__jq_$el.replaceWith($render);
this[index] = $.extend( true, this[index], data );
var $render = $(Mustache.render(this.__jqTemplate, this[index]));
$render.attr('jq-repeat-index', index);
this.__update(this[index].__jq_$el, $render, this[index], this);
this[index].__jq_$el = $render;
this.__update(this[index].__jq_$el, this[index], this);
};
result.getByKey = function(key, value){
return this[this.indexOf(key, value)];
}
result.__put = function($el, item, list){
$el.show();
@@ -213,8 +236,8 @@ MIT license
$el.remove();
};
result.__update = function($el, item, list){
console.log('here', $el)
result.__update = function($el, $render, item, list){
$el.replaceWith($render);
$el.show();
};
+53 -70
View File
@@ -1,96 +1,79 @@
( function( $ ) {
var settings = {
rule: {
eq: function( value, options ) {
var compare = $( '[name=' + options + ']' ).val();
eq: function(value, options){
var compare = $('[name=' + options + ']').val();
if ( value != compare ) {
return "Miss-match";
}
}
},
form: {
alertCount: false, //pop-up with error count
alertCountMessage: " errors!"
},
processValidation: function ( error_message, $input ) {
if ( typeof error_message == 'undefined' || error_message == true ) {
return;
}
$( '<b>' ).html( ' - ' + error_message ).appendTo( $input.siblings( 'label' ) );
$input.parent().addClass("has-error");
failedCount++;
return false;
}
};
var failedCount = 0;
$.fn.validate = function(event) {
// let thisSettings = $.extend(true, settings, settingsObj);
let hasErrors = false;
function processRule( thisSettings, $input ) {
var attr = $input.attr( 'validate' ).split( ':' ), //array of params
requirement = attr[1],
value = $input.val(), //link to input value
rule = attr[0];
if(this.is('[validate]')) return this.validateField(event);
$input.siblings( 'label' ).children( 'b' ).remove(); //removes old error
$input.parent().removeClass( "has-error" ); //removes has-error class
if(!this.attr('isValid')){
console.log('adding reset event')
this.on('reset', function(){
$(this).attr('isValid', false);
$(this).validateClear();
})
}
this.find('[validate]').each(function(){
if(!$(this).validateField()) hasErrors = true;
});
this.attr('isValid', !hasErrors);
if(hasErrors && event) event.preventDefault();
return !hasErrors;
};
$.fn.validateClear = function(){
$(this).find('input').each(function(){
$(this).removeClass('is-invalid');
$(this).removeClass('is-valid');
})
}
$.fn.validateField = function(){
var attr = this.attr('validate').split(':'); //array of params
var rule = attr[0];
var options = attr[1];
var value = this.val(); //link to input value
var message;
//checks if field is required, and length
if (isNaN(requirement) === false && requirement && value.length < requirement) {
return thisSettings.processValidation( 'Must be ' + requirement + ' characters', $input );
if(!isNaN(options) && value.length < options){
message = `Must be ${options} characters`;
}
//checks if empty to stop processing
if ( isNaN( requirement ) === false && value.length === 0 ) {
return;
if(!isNaN(options) && value.length === 0) {
}else if(rule in settings.rule){
let message = settings.rule[rule].apply(this, [value, options]);
}
if ( rule in thisSettings.rule ) {
return thisSettings.processValidation( thisSettings.rule[rule].apply( this, [value, requirement] ), $input );
}
this.validateMessage(message)
return !message;
}
$.fn.validate = function( settingsObj, event ) {
event = event || window.event;
failedCount = 0;
var thisForm = false,
thisSettings = $.extend( true, settings, settingsObj );
if ( this.is( '[validate]' ) ) {
processRule( thisSettings, this );
} else {
thisForm = true;
this.find( '[validate]' ).each( function () {
if(!processRule( thisSettings, $( this ) )){
// failedCount++;
}
});
}
this.attr('isValid', !failedCount);
if ( failedCount === 0 ) { //no errors
return true;
} else { //errors
if ( thisForm ){
if(thisSettings.form.alertCount){
alert( failedCount + thisSettings.form.alertCountMessage );
}
/* if(event) event.returnValue = false;
if(event) event.preventDefault();
return false;
if(event.preventDefault) if(event)*/
//event.returnValue = false;
event.preventDefault();
event.defaultPrevented;
}
return false;
$.fn.validateMessage = function(message){
if(message && message !== true){
this.closest('.form-group').find('b.invalid-feedback').html(message);
this.addClass('is-invalid');
}else{
this.removeClass('is-invalid');
this.addClass('is-valid');
}
return this;
};
jQuery.extend({
+1
View File
@@ -3,6 +3,7 @@
const router = require('express').Router();
const { Auth } = require('../controller/auth');
router.post('/login', async function(req, res, next){
try{
let auth = await Auth.login(req.body);
+1 -1
View File
@@ -25,7 +25,7 @@ router.post('/', async function(req, res, next){
...item,
});
} catch (error){
return next(error);
next(error);
}
});
+3
View File
@@ -40,4 +40,7 @@ router.get('/login', async function(req, res, next) {
res.render('login', {...values, redirect: req.query.redirect});
});
router.get('/test', async function(req, res, next) {
res.render('test', {...values, redirect: req.query.redirect});
});
module.exports = router;
+45 -15
View File
@@ -7,6 +7,11 @@ const sleep = require('./sleep');
// https://dns.google/resolve?name=${name}&type=TXT
AcmeClient.setLogger((message) => {
console.log('ACME:', message);
});
class LetsEncrypt{
static AcmeClient = AcmeClient;
@@ -43,32 +48,57 @@ class LetsEncrypt{
try{
domain = domain.replace(/^\*\./, '');
const [key, csr] = await AcmeClient.crypto.createCsr({
altNames: [domain, `*.${domain}`],
});
let dnsToAdd = 0;
let dnsFound = 0;
const cert = await this.client.auto({
csr,
email: 'wmantly@gmail.com',
termsOfServiceAgreed: true,
challengePriority: ['dns-01'],
skipChallengeVerification: true,
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
// console.log(`start TXT record key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`)
let resCheck = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`);
if(resCheck.data.Answer.some(record => record.data === keyAuthorization)) return;
await options.challengeCreateFn(authz, challenge, keyAuthorization);
let checkCount = 0;
while(true){
await sleep(1500);
let res = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`);
if(res.data.Answer.some(record => record.data === keyAuthorization)){
// console.log(`found record for key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`)
break;
try{
console.log('challenge', challenge)
console.log(`start TXT record key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization} challenge=${challenge} googleDNS=https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`)
dnsToAdd++
let resCheck = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`);
if(resCheck.data.Answer && resCheck.data.Answer.some(record => record.data === keyAuthorization)){
await sleep(1000);
dnsFound++
if(dnsFound === dnsToAdd){
options.onDnsCheckFound(authz, dnsFound)
}
return;
}
if(checkCount++ > 60) throw new Error('challengeCreateFn validation timed out');
await options.challengeCreateFn(authz, challenge, keyAuthorization);
let checkCount = 0;
while(true){
options.onDnsCheck(authz, checkCount);
let res = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`);
// console.log(keyAuthorization, res.data);
if(res.data.Answer && res.data.Answer.some(record => record.data === keyAuthorization)){
dnsFound++
if(dnsFound === dnsToAdd){
options.onDnsCheckFound(authz, dnsFound)
}
// console.log(`found record for key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`)
await sleep(10000);
break;
}
if(checkCount++ > 60) throw new Error('challengeCreateFn validation timed out');
await sleep(1500);
}
}catch(error){
console.log('dns check failed error:', error)
options.onDnsCheckFail(authz, error)
}
},
challengeRemoveFn: options.challengeRemoveFn,
+20 -10
View File
@@ -12,9 +12,13 @@ function ModelPs(model){
}
function publish(prop, res, req){
if(!['add', 'create', 'update', 'remove'].includes(prop)) return;
try{
if(!['add', 'create', 'update', 'remove'].includes(prop)) return;
ps.publish(`model:${Model.name}:${prop}:${getIndex(res, req)}`, res);
ps.publish(`model:${Model.name}:${prop}:${getIndex(res, req)}`, res);
}catch(error){
console.log('ModelPs.publish ERROR', error)
}
}
return new Proxy(model, {
@@ -28,16 +32,22 @@ function ModelPs(model){
const targetValue = Reflect.get(target, propKey, receiver);
if (typeof targetValue === 'function') {
return function(...args){
try{
// let res = targetValue.apply(this, args); // (A)
let res = Reflect.apply(targetValue, this, args);
if(targetValue.constructor.name === 'AsyncFunction'){
res.then(function(res){
publish(propKey, res, ...args);
});
}else{
publish(propKey, res, ...args)
var res = Reflect.apply(targetValue, this, args);
if(targetValue.constructor.name === 'AsyncFunction'){
res.then(function(res){
publish(propKey, res, ...args);
}).catch(function(error){
console.log('toDo, publish errors...')
});
}else{
publish(propKey, res, ...args)
}
return res;
}catch(error){
console.log("grrrr")
}
return res;
}
} else {
return targetValue;
+4 -2
View File
@@ -33,6 +33,8 @@ function processKeys(map, data, partial){
continue;
}
// console.log(key, data[key], map[key].default, data.hasOwnProperty(key) && data[key] !== undefined ? data[key] : returnOrCall(map[key].default))
out[key] = data.hasOwnProperty(key) && data[key] !== undefined ? data[key] : returnOrCall(map[key].default);
if(data.hasOwnProperty(key) && process_type[map[key].type]){
@@ -46,7 +48,6 @@ function processKeys(map, data, partial){
}
if(errors.length !== 0){
console.log('errors', errors)
throw new ObjectValidateError(errors);
return {__errors__: errors};
}
@@ -79,9 +80,10 @@ function parseToString(data){
return (types[typeof(data)] || String)(data);
}
function ObjectValidateError(message) {
function ObjectValidateError(message){
this.name = 'ObjectValidateError';
this.message = (message || {});
this.keys = (message || {})
this.status = 422;
}
+35 -11
View File
@@ -47,17 +47,17 @@ class Table{
}catch(error){
throw error;
}
}
static async exists(index){
try{
await this.get(data);
return true;
}catch(error){
return false;
if(typeof index === 'object'){
index = index[this._key];
}
return await client.SISMEMBER(
redisPrefix(this.prototype.constructor.name),
index
);
}
static async list(){
@@ -86,6 +86,7 @@ class Table{
static async create(data){
// Add a entry to this redis table.
try{
// Validate the passed data by the keyMap schema.
data = objValidate.processKeys(this._keyMap, data);
@@ -94,6 +95,10 @@ class Table{
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;
throw error;
@@ -107,7 +112,7 @@ class Table{
// Add the values for this entry.
for(let key of Object.keys(data)){
if(!data[key]) continue;
if(data[key] === undefined) continue;
await client.HSET(
redisPrefix(`${this.prototype.constructor.name}_${data[this._key]}`),
key,
@@ -125,9 +130,26 @@ class Table{
async update(data, key){
// Update an existing entry.
try{
// Validate the passed data, ignoring required fields.
data = objValidate.processKeys(this.constructor._keyMap, data, true);
// Check to see if entry name changed.
if(data[this.constructor._key] && data[this.constructor._key] !== this[this.constructor._key]){
// Remove the index key from the tables members list.
if(data[this.constructor._key] && await this.constructor.exists(data)){
let error = new Error('EntryNameUsed');
error.name = 'EntryNameUsed';
error.message = `${this.constructor.name}:${data[this.constructor._key]} already exists`;
error.keys = [{
key: this.constructor._key,
message: `${this.constructor.name}:${data[this.constructor._key]} already exists`
}]
error.status = 409;
throw error;
}
await client.SREM(
redisPrefix(this.constructor.name),
this[this.constructor._key]
@@ -139,12 +161,14 @@ class Table{
data[this.constructor._key]
);
await client.RENAME(
redisPrefix(`${this.constructor.name}_${this[this.constructor._key]}`),
redisPrefix(`${this.constructor.name}_${data[this.constructor._key]}`),
);
}
// Update what ever fields that where passed.
// Validate the passed data, ignoring required fields.
data = objValidate.processKeys(this.constructor._keyMap, data, true);
// Loop over the data fields and apply them to redis
for(let key of Object.keys(data)){
this[key] = data[key];
+100 -76
View File
@@ -5,7 +5,7 @@
</script>
<style type="text/css">
label.control-label{
label.form-label{
font-weight: bold;
margin-bottom: 1px;
}
@@ -14,7 +14,10 @@
font-weight: bold;
}
div.editWindow{
div.hostEditPanel{
margin-bottom: 1em;
}
div.form-group{
margin-bottom: 1em;
}
/* my Div class for my search bar */
@@ -36,11 +39,13 @@
</style>
<script type="text/javascript">
var $editHostForm;
function parseHostRow(host) {
host['updated_on_text'] = moment(host['updated_on'], "x").fromNow();
host['targetssl_text'] = host['targetssl'] ? 'https://' : 'http://';
host['forcessl_text'] = host['forcessl'] ? 'https://' : 'http://';
host['wildcard_text'] = host['is_wildcard'] ? host['wildcard_status'] : 'Auto';
return host;
}
@@ -66,40 +71,34 @@
$('#hostsTable').find('tr').each(function(idx, el){
$(el).removeClass('table-warning');
});
$('.editWindow').slideUp('fast');
$.scope.editHost.remove();
}
function editHost(btn, host){
cancleHostEdit();
$(btn).closest('tr').addClass('table-warning');
$('.editWindow .card-title').html("Edit "+ host);
host = $.scope.hosts.getByKey(host);
$.scope.editHost.update({...host, form: $editHostForm.html()});
$('div.editWindow .card-body span').html($('#addHost').html());
$('div.editWindow .card-body span button').remove();
$(".editWindow input[name='edit_host']").val(host);
$('.editWindow').slideDown('fast');
app.host.get(host, function(error, data){
$.each( data.results, function( key, value ) {
if(typeof value == "boolean"){
$(".editWindow #"+ key +"-"+ value).prop('checked', true)
}else{
$(".editWindow input[name='" + key + "']").val(value);
}
});
$.each(host, function( key, value ) { if(typeof value == "boolean"){
$(".hostEditPanel #"+ key +"-"+ value).prop('checked', true)
}else{
$(".hostEditPanel input[name='" + key + "']").val(value);
}
});
};
function deleteHost(host){
app.host.remove({host: host}, function(err, data){
// app.util.actionMessage(`Host ${host} deleted!`, $.scope.hosts.$this, 'danger');
$.scope.hosts.remove(host);
app.host.remove({host: host}, function(error, data){
if(error) app.util.actionMessage(error.message, $.scope.hosts.$this, 'danger');
});
}
$(document).ready(function(){
$editHostForm = $('#addHost').clone();
$editHostForm.find('hr.buttonBreak').nextAll().remove();
// $editHostForm.find('.autoSll').addClass('bg-secondary');
$editHostForm.find('[name=is_wildcard').attr('disabled', true);
populateHosts(); //populate the table
$.scope.hosts.__setTake(function($el, item, list){
@@ -109,6 +108,19 @@
});
});
$.scope.hosts.__setUpdate(function($el, $render, item, list){
$render.show()
$el.replaceWith($render);
});
$.scope.editHost.__setPut(function($el, item, list){
$el.slideDown();
});
$.scope.editHost.__setTake(function($el, item, list){
$el.slideUp();
});
app.subscribe(/^model:Host/, function(data, topic){
console.log(topic, data);
});
@@ -119,7 +131,7 @@
if($.scope.hosts.indexOf(host) >= 0){
$.scope.hosts.update(host, parseHostRow(data));
}else{
$.scope.hosts.splice(0,0, parseHostRow(data))
$.scope.hosts.unshift(parseHostRow(data));
}
});
@@ -129,11 +141,10 @@
if($.scope.hosts.indexOf(host) >= 0){
$.scope.hosts.update(host, parseHostRow(data));
}else{
$.scope.hosts.splice(0,0, parseHostRow(data))
$.scope.hosts.unshift(parseHostRow(data));
}
});
app.subscribe(/^model:Host:remove/, function(data, topic){
let [a,b, action, host] = topic.split(':');
@@ -156,38 +167,6 @@
}
}
});
$('form.addHost').on('submit', function(){
event.preventDefault();
$form = $(this);
var action = $($form.find('button[type="submit"]')[0]).data('type');
app.util.actionMessage('', $form);
if($form.attr('isValid') === 'true'){
var formdata = $form.serializeObject();
if(formdata.targetPort) formdata.targetPort = Number(formdata.targetPort);
if(formdata.targetssl) formdata.targetssl = formdata.targetssl == 'true' ? true : false;
if(formdata.forcessl) formdata.forcessl = formdata.forcessl == 'true' ? true : false;
app.host[action](formdata, function(error, data){
if(error){
app.util.actionMessage(error + data.message, $form, 'danger');
return;
}
// if($.scope.hosts.indexOf(data.__requestedHost) >= 0){
// $.scope.hosts.update(data.__requestedHost, parseHostRow(data));
// }else{
// // $.scope.hosts.splice(0,0, parseHostRow(data))
// }
if(action == 'edit') $('.editWindow').slideUp('fast');
$form.trigger('reset');
})
}
});
});
</script>
<div class="row" style="display:none">
@@ -195,7 +174,7 @@
<!--
left column
-->
<div class="card shadow-lg editWindow" style="display:none">
<div jq-repeat="editHost" class="card shadow-lg border-warning hostEditPanel" style="display:none">
<!--
Edit host card
-->
@@ -204,6 +183,7 @@
<i class="fa-solid fa-pencil"></i>
</span>
<span class="card-title">
Edit {{ host }}
</span>
<span class="float-end">
<i class="fa-solid fa-circle-minus"></i>
@@ -212,8 +192,8 @@
</div>
<div class="card-body">
<form class="addHost" onsubmit="$(this).validate()">
<span></span>
<form class="addHost" method="PUT" action="/host/{{ host }}" onsubmit="formAJAX(this)" evalAJAX="cancleHostEdit()">
{{{ form }}}
<input type="hidden" name="edit_host" />
<button type="submit" data-type="edit" class="btn btn-warning">
<i class="fa-solid fa-pencil"></i>
@@ -227,7 +207,7 @@
</div>
</div>
<div class="card shadow-lg">
<div class="card shadow-lg hostAddPanel">
<!--
Add new host card
-->
@@ -246,10 +226,13 @@
<div class="card-header actionMessage" style="display:none"></div>
<div class="card-body">
<form class="addHost" id="addHost" onsubmit="$(this).validate()">
<form class="addHost" id="addHost" method="POST" action="host" onsubmit="formAJAX(this)">
<div class="form-group">
<label class="control-label">Incoming SSL</label>
<label class="form-label">
Incoming SSL
</label>
<br />
<div class="radio">
<label>
<input type="radio" name="forcessl" id="forcessl-true" value="true" checked>
@@ -264,23 +247,54 @@
</div>
</div>
<div class="form-group">
<label class="control-label">Incoming Host Name</label>
<input type="text" name="host" class="form-control" placeholder="ex: proxy.cloud-ops.net" validate=":3" >
<div class="form-group autoSll">
<label class="form-label">
Auto SSL
</label>
<div class="radio">
<label>
<input type="radio" name="is_wildcard" id="is_wildcard-false" value="false" checked>
On demand certs
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="is_wildcard" id="is_wildcard-true" value="true">
Request Wildcard cert
</label>
</div>
</div>
<div class="form-group">
<label class="control-label">Target IP or Host Name</label>
<label for='host' class="form-label">
Incoming Host Name
</label>
<div>
<input type="text" name="host" class="form-control" placeholder="ex: proxy.cloud-ops.net" validate=":3" >
<b class="invalid-feedback"></b>
</div>
</div>
<div class="mb-3 form-group">
<label for="ip" class="form-label">
Target IP or Host Name
</label>
<input type="text" name="ip" class="form-control" placeholder="ex: 10.10.10.10" validate=":3" />
<b class="invalid-feedback"></b>
</div>
<div class="form-group">
<label class="control-label">Target TCP Port</label>
<label for="targetPort" class="form-label">
Target TCP Port
</label>
<input type="number" name="targetPort" class="form-control" value="80" min="0" max="65535" />
<b class="invalid-feedback"></b>
</div>
<div class="form-group">
<label class="control-label">Target SSL</label>
<label class="form-label">
Target SSL
</label>
<div class="radio">
<label>
<input type="radio" name="targetssl" id="targetssl-true" value="true">
@@ -293,9 +307,10 @@
Proxy to HTTP <b>Recommended</b>
</label>
</div>
<b class="invalid-feedback"></b>
</div>
<hr />
<button type="submit" data-type="add" class="btn btn-success">
<hr class="buttonBreak" />
<button type="submit" class="btn btn-success">
<i class="fa-solid fa-plus"></i>
Add
</button>
@@ -308,7 +323,7 @@
<!--
Right column
-->
<div class="card shadow-lg">
<div class="card shadow-lg hostListPanel">
<!--
List current hosts
-->
@@ -326,12 +341,17 @@
<div class="card-header actionMessage" style="display:none"></div>
<div class="card-body search-wrapper">
<label class="control-label" for="search" style="margin-left: -5px;">Search Hosts: </label>
<label class="form-label" for="search" style="margin-left: -5px;">
Search Hosts:
</label>
<input type="search" id="search" host-search>
</div>
<table class="card-body table table-striped" style="margin-bottom:0">
<thead>
<th>
SSL
</th>
<th>
Host Name
</th>
@@ -347,6 +367,9 @@
</thead>
<tbody id="hostsTable">
<tr action="api" jq-repeat="hosts" jq-repeat-index='host' style="display:none">
<td>
{{{ wildcard_text }}}
</td>
<td>
<a target="_blank" href="{{ forcessl_text }}{{ host }}">
{{{ forcessl_text }}}{{ host }}
@@ -360,12 +383,13 @@
</td>
<td>
<div class="btn-group">
<!-- <button type="button" class="btn btn-info">
<!-- <button type="button" class="btn btn-info">
<i class="fa-brands fa-expeditedssl"></i>
Cert
</button> -->
<button type="button" onclick="editHost(this, '{{ host }}');" class="btn btn-sm btn-warning">
<i class="fa-solid fa-pencil"></i> Edit
<i class="fa-solid fa-pencil"></i>
Edit
</button>
<button type="button" onclick="deleteHost('{{ host }}')" class="btn btn-sm btn-danger">
<i class="fa-solid fa-trash-can"></i>
+74
View File
@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<!-- need to load jq-query cdn or file. -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<!-- need to load mustache cdn or file. -->
<script src="
https://cdn.jsdelivr.net/npm/mustache@4.2.0/mustache.min.js
"></script>
<!-- need to load jq-repeat cdn or file -->
<script type="text/javascript" src='/static/lib/js/jq-repeat_new.js'></script>
<script>
//on document ready. the logic would execute.
$(document).ready(function () {
$.scope.toDo.__setPut(function($el, item, list){
$el.slideDown('slow');
})
// $.scope.toDo.__setUpdate(function($el, $render, item, list){
// $el.fadeOut(2000, function() {
// $(this).html($render.html()).fadeIn(2000);
// });
// });
// $.scope.toDo.__setUpdate(function($el, $render, item, list){
// $el.animate({'opacity': 0}, 400, function(){
// $(this).html($render.html()).animate({'opacity': 1}, 400);
// });
// });
$.scope.toDo.__setUpdate(function($el, $render, item, list){
$el.slideUp(function(){
$(this).replaceWith($render)
$render.slideDown()
})
});
$.scope.toDo.push({ item: "Get milk", done: "Yes" }); // 0
$.scope.toDo.push({ item: "Do laundry", done: "No" }); // 1
//should take array id or array key
// - **howMany** _Type: Number_
// Number of repeat objects that will be removed. If there are non to be removed, it is not required to use this argument.
// - **update** _Type: Array_
// This is the array of repeat objects to add. If there are none to this is not required.
//remove works
// $.scope.toDo.splice("Get milk" , "1")
//update
$.scope.toDo.splice(-1,0, { item: "Get Bread", done: "Yes" })
});
</script>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<ul>
<li jq-repeat="toDo" jq-repeat-index="item" style="display:none;"><span class="item">{{ item }}</span>: {{ done }}</li>
</ul>
</body>
</html>
+2 -2
View File
@@ -67,7 +67,7 @@
<div class="card-header actionMessage" style="display:none"></div>
<div class="card-body">
<form action="user/" evalAJAX="
<form action="user/" onsubmit="formAJAX(this)" evalAJAX="
$.scope.users.splice(0, 0, data);
">
<input type="hidden" class="form-control" name="delete" value="false" />
@@ -84,7 +84,7 @@
<input type="password" class="form-control" name="passwordMatch" placeholder="Retype password" validate="eq:password"/>
</div>
<hr />
<button type="button" onclick="formAJAX(this)" class="btn btn-info">
<button type="button" class="btn btn-info">
Add
</button>
</form>