From b27e1b1617344f6395caa62ec190a02cc3387ef6 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Tue, 13 Aug 2024 15:18:47 -0400 Subject: [PATCH] Added relations to redis ORM --- nodejs/controller/auth.js | 5 +- nodejs/middleware/auth.js | 4 +- .../{dnsProvider.js => dns_provider.js} | 70 +++++++++++------ nodejs/models/dns_provider/cloudflare.js | 13 ++-- nodejs/models/dns_provider/common.js | 47 ++++++++++- nodejs/models/dns_provider/digitalocean.js | 7 +- nodejs/models/dns_provider/porkbun.js | 16 ++-- nodejs/models/host.js | 10 +-- nodejs/models/index.js | 9 +++ nodejs/models/token.js | 21 +++-- nodejs/models/user_redis.js | 5 +- nodejs/public/lib/js/jq-repeat_new.js | 3 +- nodejs/routes/dns.js | 13 +++- nodejs/routes/host.js | 4 +- nodejs/routes/user.js | 2 +- nodejs/utils/model_pubsub.js | 8 +- nodejs/utils/redis_model.js | 78 ++++++++++++++++--- nodejs/views/dns.ejs | 17 ++-- nodejs/views/hosts.ejs | 14 ++-- nodejs/views/top.ejs | 44 ----------- 20 files changed, 240 insertions(+), 150 deletions(-) rename nodejs/models/{dnsProvider.js => dns_provider.js} (71%) create mode 100644 nodejs/models/index.js diff --git a/nodejs/controller/auth.js b/nodejs/controller/auth.js index 0749b98..d117173 100644 --- a/nodejs/controller/auth.js +++ b/nodejs/controller/auth.js @@ -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(); } } diff --git a/nodejs/middleware/auth.js b/nodejs/middleware/auth.js index 49c2ca7..802d7d1 100755 --- a/nodejs/middleware/auth.js +++ b/nodejs/middleware/auth.js @@ -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); diff --git a/nodejs/models/dnsProvider.js b/nodejs/models/dns_provider.js similarity index 71% rename from nodejs/models/dnsProvider.js rename to nodejs/models/dns_provider.js index 7d79cec..16c6a38 100644 --- a/nodejs/models/dnsProvider.js +++ b/nodejs/models/dns_provider.js @@ -9,9 +9,9 @@ const ModelPs = require('../utils/model_pubsub'); const tldExtract = require('tld-extract').parse_host; const providers = { - PorkBun: require('./dns_provider/porkbun'), - DigitalOcean: require('./dns_provider/digitalocean'), Cloudflare: require('./dns_provider/cloudflare'), + DigitalOcean: require('./dns_provider/digitalocean'), + PorkBun: require('./dns_provider/porkbun'), }; class Domain extends Table{ @@ -23,17 +23,22 @@ class Domain extends Table{ '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(data, ...args){ - let instance = await super.get(data, ...args); - // instance.provider = (await DnsProvider.get(instance.dnsProvider_id)).provider; + static async get(domain, ...args){ + try{ + domain = tldExtract(domain).domain; + }catch{} + + let instance = await super.get(domain, ...args); return instance; } - static async getByProviderId(id){ - let domains = await this.listDetail(); + static async getByProviderId(id, ...args){ + let domains = await this.listDetail(...args); let results = []; for(let domain of domains){ if(domain.dnsProvider_id == id) results.push(domain); @@ -41,8 +46,21 @@ class Domain extends Table{ return results; } + + // toJSON(){ + // return { + // ...super.toJSON(), + // provider: { + // displayName: this.provider.constructor.displayName, + // displayIconUni: this.provider.constructor.displayIconUni, + // displayIconHtml: this.provider.constructor.displayIconHtml, + // } + // } + // } } +Domain.register(ModelPs(Domain)) + class DnsProvider extends Table{ static _key = 'id'; static _keyMap = { @@ -53,7 +71,7 @@ class DnsProvider extends Table{ 'id': {default: ()=>crypto.randomBytes(8).toString("hex")}, 'name': {isRequired: true, type: 'string'}, 'dnsProvider': {isRequired: true, type: 'string'}, - 'zoneId': {isRequired: false, type: 'string'}, + 'domains': {model:'Domain', rel: 'many', remoteKey: 'dnsProvider_id'} } static __intraModel(provider){ @@ -74,23 +92,16 @@ class DnsProvider extends Table{ static async create(data, ...args){ let __intraModel = this.__intraModel(data.dnsProvider) - let provider = new __intraModel.Provider(data); - let res = await provider.listDomains(); + let provider = new __intraModel.Provider(data, ...args); - let instance = await super.create.call(__intraModel, data); - if(data.noUpdate !== false) await instance.updateDomains(); - - return instance; + return await super.create.call(__intraModel, data, ...args); } static async get(data, ...args){ - let instance = await super.get(data); + let instance = await super.get(data, ...args); let __intraModel = this.__intraModel(instance.dnsProvider) - instance = await super.get.call(__intraModel, data); - instance.provider = new __intraModel.Provider(instance); - - return instance; + return await super.get.call(__intraModel, data, ...args); } static listProviders(){ @@ -138,24 +149,33 @@ class DnsProvider extends Table{ toJSON(){ return { ...super.toJSON(), - displayName: this.provider.constructor.displayName, - displayIconHtml: this.provider.constructor.displayIconHtml, - displayIconUni: this.provider.constructor.displayIconUni, + ...this.constructor.Provider.toJSON() }; } } -Domain = ModelPs(Domain); -DnsProvider = ModelPs(DnsProvider); +DnsProvider.register(ModelPs(DnsProvider)) -module.exports = {Domain, DnsProvider}; if(require.main === module){(async function(){try{ const conf = require('../conf'); + console.log(Table.models) + + // console.log(await Domain.listDetail()) + + // console.log(JSON.stringify(await Domain.get('ipa.wtf'), null, 2)) + + // console.log(JSON.stringify(await Domain.listDetail() ,null, 2)) + console.log(JSON.stringify(await Table.models.DnsProvider.listDetail() ,null, 2)) + + + // console.log(await Domain.getByProviderId('e8443e03ac503c7b')); // console.log(aw) + process.exit(0) + }catch(error){ console.log('IIFE Error:', error); }})()} diff --git a/nodejs/models/dns_provider/cloudflare.js b/nodejs/models/dns_provider/cloudflare.js index 7ecd775..67f639a 100644 --- a/nodejs/models/dns_provider/cloudflare.js +++ b/nodejs/models/dns_provider/cloudflare.js @@ -1,28 +1,29 @@ 'use strict'; const axios = require('axios'); -const {dnsErrors} = require('./common'); +const {dnsErrors, DnsApi} = require('./common'); //like the options obj will always use domain data and type // change content to data // change name to domain -class CloudFlare{ +class CloudFlare extends DnsApi{ static _keyMap = { token: {isRequired: true, type: 'string', isPrivate: true, displayName: 'API Token'}, } static displayName = 'CloudFlare' static displayIconHtml = ` - - - - ` + + + + ` // Cloud icon for cloudflare static displayIconUni = '' constructor(token){ + super() this.token = token.token || token; } diff --git a/nodejs/models/dns_provider/common.js b/nodejs/models/dns_provider/common.js index 59bfb3f..067d451 100644 --- a/nodejs/models/dns_provider/common.js +++ b/nodejs/models/dns_provider/common.js @@ -19,7 +19,52 @@ let dnsErrors = { }, } +class DnsApi{ + errors = { + unauthorized(instance){ + let error = new Error('UnauthorizedDnsApi'); + error.name = 'UnauthorizedDnsApi'; + error.message = `Unauthorized call to ${instance.constructor ? instance.constructor.name : instance.name}`; + error.status = 424; + + return error; + }, + other(instance, status, message){ + let error = new Error('OtherDnsApiError'); + error.name = 'OtherDnsApiError'; + error.message = `DNS API Error ${instance.constructor ? instance.constructor.name : instance.name}: `; + error.status = 424; + + 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(), + } + } + + toJSON(){ + return this.constructor.toJSON() + } +} + module.exports = { - dnsErrors + dnsErrors, + DnsApi, }; diff --git a/nodejs/models/dns_provider/digitalocean.js b/nodejs/models/dns_provider/digitalocean.js index 8b32add..0b4f65c 100644 --- a/nodejs/models/dns_provider/digitalocean.js +++ b/nodejs/models/dns_provider/digitalocean.js @@ -1,16 +1,16 @@ 'use strict'; const axios = require('axios'); -const {dnsErrors} = require('./common'); +const {dnsErrors, DnsApi} = require('./common'); -class DigitalOcean{ +class DigitalOcean extends DnsApi{ static _keyMap = { token: {isRequired: true, type: 'string', isPrivate: true, displayName: 'API Token'}, } static displayName = 'DigitalOcean' static displayIconHtml = ` - + ` @@ -18,6 +18,7 @@ class DigitalOcean{ static displayIconUni = '' constructor(token){ + super() this.token = token.token || token; } diff --git a/nodejs/models/dns_provider/porkbun.js b/nodejs/models/dns_provider/porkbun.js index a39189e..a536da3 100644 --- a/nodejs/models/dns_provider/porkbun.js +++ b/nodejs/models/dns_provider/porkbun.js @@ -1,18 +1,19 @@ 'use strict'; const axios = require('axios'); -const {dnsErrors} = require('./common'); +const {dnsErrors, DnsApi} = require('./common'); -class PorkBun{ +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 = 'DigitalOcean'; + static displayName = 'PorkBun'; + static displayIconUni = ''; static displayIconHtml = ` - + @@ -24,12 +25,9 @@ class PorkBun{ ` - // ''; - static displayIconUni = ''; - - baseUrl = 'https://api.porkbun.com/api/json/v3'; constructor(args){ + super() this.apiKey = args.apiKey; this.secretApiKey = args.secretApiKey; } @@ -42,7 +40,7 @@ class PorkBun{ secretapikey: this.secretApiKey, apikey: this.apiKey, }; - res = await axios.post(`${this.baseUrl}${url}`, data); + res = await axios.post(`'https://api.porkbun.com/api/json/v3'${url}`, data); return res; }catch(error){ diff --git a/nodejs/models/host.js b/nodejs/models/host.js index c08240c..fe9548d 100755 --- a/nodejs/models/host.js +++ b/nodejs/models/host.js @@ -1,18 +1,12 @@ 'use strict'; -const Table = require('../utils/redis_model'); +const Table = require('.'); const ModelPs = require('../utils/model_pubsub'); const tldExtract = require('tld-extract').parse_host; -const PorkBun = require('./dns_provider/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, -// }); - class Host extends Table{ static _key = 'host'; @@ -31,6 +25,7 @@ class Host extends Table{ 'wildcard_status': {isRequired: false, type: 'string', min: 3, max: 500, default: 'Requesting'}, 'wildcard_parent': {isRequired: false, type: 'string', min: 3, max: 500}, 'wildcard_expires': {isRequired: false, type: 'number'}, + 'domain': {model: 'Domain', rel: 'one'}, } static lookUpObj = {}; @@ -322,6 +317,7 @@ class Host extends Table{ return true; } } +Host.register(ModelPs(Host)) class Cached extends Table{ diff --git a/nodejs/models/index.js b/nodejs/models/index.js new file mode 100644 index 0000000..36b62cc --- /dev/null +++ b/nodejs/models/index.js @@ -0,0 +1,9 @@ +'use strict'; + +const Table = require('../utils/redis_model') +module.exports = Table; + +require('./dns_provider'); +require('./host'); +require('./token'); +require('./user'); diff --git a/nodejs/models/token.js b/nodejs/models/token.js index ee566a2..d108c1a 100644 --- a/nodejs/models/token.js +++ b/nodejs/models/token.js @@ -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}; diff --git a/nodejs/models/user_redis.js b/nodejs/models/user_redis.js index a096ded..e94a1d0 100644 --- a/nodejs/models/user_redis.js +++ b/nodejs/models/user_redis.js @@ -58,12 +58,9 @@ class User extends Table{ throw error; } }; - - } -module.exports = {User}; - +User.register(); (async function(){ var defaultUser = 'proxyadmin2' diff --git a/nodejs/public/lib/js/jq-repeat_new.js b/nodejs/public/lib/js/jq-repeat_new.js index 48be59a..f55dcb0 100644 --- a/nodejs/public/lib/js/jq-repeat_new.js +++ b/nodejs/public/lib/js/jq-repeat_new.js @@ -264,7 +264,7 @@ MIT license return { ...this.__parseData(data), nestedTemplates: this.__parseNestedTemplates(index, data), - _parent: this.__jqParent ? $.scope[this.__jqParent][this.__jqParentIndex] : undefined, + _parent: this._parentData, }; }; @@ -307,6 +307,7 @@ MIT license 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'); } diff --git a/nodejs/routes/dns.js b/nodejs/routes/dns.js index 9a9c502..e091fb7 100644 --- a/nodejs/routes/dns.js +++ b/nodejs/routes/dns.js @@ -1,7 +1,7 @@ 'use strict'; const router = require('express').Router(); -const {DnsProvider, Domain} = require('../models/dnsProvider'); +const {DnsProvider, Domain} = require('../models').models; const Model = DnsProvider; @@ -49,6 +49,7 @@ router.get('/domain', async function(req, res, next){ } }); + router.get('/domain/byProvider/:item', async function(req, res, next){ try{ return res.json({ @@ -68,6 +69,16 @@ router.post('/domain/refresh/:item', async function(req, res, next){ } }) +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{ diff --git a/nodejs/routes/host.js b/nodejs/routes/host.js index af001fd..4e7be89 100755 --- a/nodejs/routes/host.js +++ b/nodejs/routes/host.js @@ -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); diff --git a/nodejs/routes/user.js b/nodejs/routes/user.js index 84edd21..04d1dc8 100755 --- a/nodejs/routes/user.js +++ b/nodejs/routes/user.js @@ -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{ diff --git a/nodejs/utils/model_pubsub.js b/nodejs/utils/model_pubsub.js index 924fb4f..8e0b823 100644 --- a/nodejs/utils/model_pubsub.js +++ b/nodejs/utils/model_pubsub.js @@ -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; @@ -46,7 +44,7 @@ function ModelPs(model){ } return res; }catch(error){ - console.log("grrrr") + console.log("grrrr", error) } } } else { diff --git a/nodejs/utils/redis_model.js b/nodejs/utils/redis_model.js index 3dc7af5..c712598 100644 --- a/nodejs/utils/redis_model.js +++ b/nodejs/utils/redis_model.js @@ -5,14 +5,37 @@ 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 redisClient = client; + + static models = {} + static register = function(Model){ + Model = Model || this; + this.models[Model.name] = Model; + } constructor(data){ for(let key in data){ @@ -20,9 +43,8 @@ class Table{ } } - static async get(index){ + static async get(index, queryHelper){ try{ - if(typeof index === 'object'){ index = index[this._key]; } @@ -43,13 +65,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]; @@ -73,12 +119,21 @@ 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; @@ -213,8 +268,9 @@ class Table{ toJSON(){ let result = {}; - for (const [key, keyProps] of Object.entries(this.constructor._keyMap)) { - if(!keyProps.isPrivate) result[key] = this[key]; + for (const [key, value] of Object.entries(this)) { + if(this.constructor._keyMap[key] && this.constructor._keyMap[key].isPrivate) continue; + result[key] = value; } return result @@ -222,6 +278,10 @@ class Table{ // return JSON.stringify(result); } + toString(){ + return this[this.constructor._key]; + } + } diff --git a/nodejs/views/dns.ejs b/nodejs/views/dns.ejs index 70dd325..81d9b6c 100644 --- a/nodejs/views/dns.ejs +++ b/nodejs/views/dns.ejs @@ -59,16 +59,10 @@ $.scope.DnsProvider.parseData = function(row){ row['created_on_text'] = moment(row['updated_on'], "x").fromNow(); - + row['domainsString'] = JSON.stringify(row.domains); return row }; - (async function(){ - - })() - - - $(document).ready(async function(){ // Set the jq Templates $.scope.providerField.put = function(){}; @@ -102,7 +96,6 @@ $.scope[Model].update(pk, data) }catch{} }); - }); @@ -182,7 +175,7 @@ diff --git a/nodejs/views/hosts.ejs b/nodejs/views/hosts.ejs index 5582a52..f71a0ea 100755 --- a/nodejs/views/hosts.ejs +++ b/nodejs/views/hosts.ejs @@ -62,10 +62,10 @@ } 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)); } @@ -150,9 +150,9 @@ $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(':'); @@ -391,6 +391,10 @@ {{{ forcessl_text }}}{{ host }} + {{#domain.provider}} +
+ {{displayName}} + {{/domain.provider}} {{{ targetssl_text }}}{{ ip }}:{{ targetPort }} diff --git a/nodejs/views/top.ejs b/nodejs/views/top.ejs index d14cfbb..b744441 100755 --- a/nodejs/views/top.ejs +++ b/nodejs/views/top.ejs @@ -31,55 +31,11 @@ - - - - - -