The great refactor (#82)
This commit is contained in:
@@ -6,9 +6,9 @@
|
||||
import compression from "compression"
|
||||
import cache from "memory-cache"
|
||||
import util from "util"
|
||||
import setup from "../setup.mjs"
|
||||
import mocks from "../mocks.mjs"
|
||||
import metrics from "../metrics.mjs"
|
||||
import setup from "../metrics/setup.mjs"
|
||||
import mocks from "../mocks/index.mjs"
|
||||
import metrics from "../metrics/index.mjs"
|
||||
|
||||
/** App */
|
||||
export default async function ({mock, nosettings} = {}) {
|
||||
@@ -66,8 +66,11 @@
|
||||
|
||||
//Base routes
|
||||
const limiter = ratelimit({max:debug ? Number.MAX_SAFE_INTEGER : 60, windowMs:60*1000})
|
||||
const metadata = Object.fromEntries(Object.entries(conf.metadata.plugins)
|
||||
.filter(([key]) => !["base", "core"].includes(key))
|
||||
.map(([key, value]) => [key, Object.fromEntries(Object.entries(value).filter(([key]) => ["name", "icon", "web", "supports"].includes(key)))]))
|
||||
const enabled = Object.entries(metadata).map(([name]) => ({name, enabled:plugins[name]?.enabled ?? false}))
|
||||
const templates = Object.entries(Templates).map(([name]) => ({name, enabled:(conf.settings.templates.enabled.length ? conf.settings.templates.enabled.includes(name) : true) ?? false}))
|
||||
const enabled = Object.entries(Plugins).map(([name]) => ({name, enabled:plugins[name]?.enabled ?? false}))
|
||||
const actions = {flush:new Map()}
|
||||
let requests = (await rest.rateLimit.get()).data.rate
|
||||
setInterval(async () => requests = (await rest.rateLimit.get()).data.rate, 30*1000)
|
||||
@@ -80,6 +83,7 @@
|
||||
//Plugins and templates
|
||||
app.get("/.plugins", limiter, (req, res) => res.status(200).json(enabled))
|
||||
app.get("/.plugins.base", limiter, (req, res) => res.status(200).json(conf.settings.plugins.base.parts))
|
||||
app.get("/.plugins.metadata", limiter, (req, res) => res.status(200).json(metadata))
|
||||
app.get("/.templates", limiter, (req, res) => res.status(200).json(templates))
|
||||
app.get("/.templates/:template", limiter, (req, res) => req.params.template in conf.templates ? res.status(200).json(conf.templates[req.params.template]) : res.sendStatus(404))
|
||||
for (const template in conf.templates)
|
||||
@@ -148,7 +152,7 @@
|
||||
//Compute rendering
|
||||
try {
|
||||
//Render
|
||||
const q = parse(req.query)
|
||||
const q = req.query
|
||||
console.debug(`metrics/app/${login} > ${util.inspect(q, {depth:Infinity, maxStringLength:256})}`)
|
||||
const {rendered, mime} = await metrics({login, q}, {
|
||||
graphql, rest, plugins, conf,
|
||||
@@ -195,19 +199,3 @@
|
||||
`Server ready !`
|
||||
].join("\n")))
|
||||
}
|
||||
|
||||
/** Query parser */
|
||||
function parse(query) {
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
//Parse number
|
||||
if (/^\d+$/.test(value))
|
||||
query[key] = Number(value)
|
||||
//Parse boolean
|
||||
if (/^(?:true|false)$/.test(value))
|
||||
query[key] = (value === "true")||(value === true)
|
||||
//Parse null
|
||||
if (/^null$/.test(value))
|
||||
query[key] = null
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
29
source/app/web/settings.example.json
Normal file
29
source/app/web/settings.example.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"//": "Example of configuration for metrics web instance",
|
||||
"//": "====================================================================",
|
||||
|
||||
"token": "MY GITHUB API TOKEN", "//": "GitHub Personal Token (required)",
|
||||
"restricted": [], "//": "Authorized users (empty to disable)",
|
||||
"maxusers": 0, "//": "Maximum users, (0 to disable)",
|
||||
"cached": 3600000, "//": "Cache time rendered metrics (0 to disable)",
|
||||
"ratelimiter": null, "//": "Rate limiter (see express-rate-limit documentation)",
|
||||
"port": 3000, "//": "Listening port",
|
||||
"optimize": true, "//": "SVG optimization",
|
||||
"debug": false, "//": "Debug logs",
|
||||
"mocked": false, "//": "Use mocked data instead of live APIs",
|
||||
"repositories": 100, "//": "Number of repositories to use",
|
||||
"community": {
|
||||
"templates": [], "//": "Additional community templates to setup"
|
||||
},
|
||||
"templates": {
|
||||
"default": "classic", "//": "Default template",
|
||||
"enabled": [], "//": "Enabled templates (empty to enable all)"
|
||||
},
|
||||
"plugins": { "//": "Global plugin configuration",
|
||||
<% for (const name of Object.keys(plugins).filter(v => !["base", "core"].includes(v))) { -%>
|
||||
"<%= name %>":{
|
||||
<%- JSON.stringify(Object.fromEntries(Object.entries(plugins[name].inputs).filter(([key, {type}]) => type === "token").map(([key, {description:value}]) => [key.replace(new RegExp(`^plugin_${name}_`), ""), value])), null, 6).replace(/^[{]/gm, "").replace(/^\s*[}]$/gm, "").replace(/": "/gm, `${'": null,'.padEnd(22)} "//":"`).replace(/"$/gm, '",').trimStart().replace(/\n$/gm, "\n ") %>"enabled": false, "//": "<%= plugins[name].inputs[`plugin_${name}`].description %>"
|
||||
},
|
||||
<% } %>"//": ""
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
//Init
|
||||
const {data:templates} = await axios.get("/.templates")
|
||||
const {data:plugins} = await axios.get("/.plugins")
|
||||
const {data:metadata} = await axios.get("/.plugins.metadata")
|
||||
const {data:base} = await axios.get("/.plugins.base")
|
||||
const {data:version} = await axios.get("/.version")
|
||||
templates.sort((a, b) => (a.name.startsWith("@") ^ b.name.startsWith("@")) ? (a.name.startsWith("@") ? 1 : -1) : a.name.localeCompare(b.name))
|
||||
@@ -60,111 +61,20 @@
|
||||
list:plugins,
|
||||
enabled:{base:Object.fromEntries(base.map(key => [key, true]))},
|
||||
descriptions:{
|
||||
pagespeed:"⏱️ Website performances",
|
||||
languages:"🈷️ Most used languages",
|
||||
followup:"🎟️ Issues and pull requests",
|
||||
traffic:"🧮 Pages views",
|
||||
lines:"👨💻 Lines of code changed",
|
||||
habits:"💡 Coding habits",
|
||||
music:"🎼 Music plugin",
|
||||
posts:"✒️ Recent posts",
|
||||
isocalendar:"📅 Isometric commit calendar",
|
||||
gists:"🎫 Gists metrics",
|
||||
topics:"📌 Starred topics",
|
||||
projects:"🗂️ Projects",
|
||||
tweets:"🐤 Latest tweets",
|
||||
stars:"🌟 Recently starred repositories",
|
||||
stargazers:"✨ Stargazers over last weeks",
|
||||
activity:"📰 Recent activity",
|
||||
people:"🧑🤝🧑 People",
|
||||
anilist:"🌸 Anilist",
|
||||
base:"🗃️ Base content",
|
||||
"base.header":"Header",
|
||||
"base.activity":"Account activity",
|
||||
"base.community":"Community stats",
|
||||
"base.repositories":"Repositories metrics",
|
||||
"base.metadata":"Metadata",
|
||||
...Object.fromEntries(Object.entries(metadata).map(([key, {name}]) => [key, name]))
|
||||
},
|
||||
options:{
|
||||
descriptions:{
|
||||
"languages.ignored":{text:"Ignored languages", placeholder:"lang-0, lang-1, ..."},
|
||||
"languages.skipped":{text:"Skipped repositories", placeholder:"repo-0, repo-1, ..."},
|
||||
"languages.colors":{text:"Custom language colors", placeholder:"0:#ff0000, javascript:yellow, ..."},
|
||||
"pagespeed.detailed":{text:"Detailed audit", type:"boolean"},
|
||||
"pagespeed.screenshot":{text:"Audit screenshot", type:"boolean"},
|
||||
"pagespeed.url":{text:"Url", placeholder:"(default to GitHub attached)"},
|
||||
"habits.from":{text:"Events to use", type:"number", min:1, max:1000},
|
||||
"habits.days":{text:"Max events age", type:"number", min:1, max:30},
|
||||
"habits.facts":{text:"Display facts", type:"boolean"},
|
||||
"habits.charts":{text:"Display charts", type:"boolean"},
|
||||
"music.provider":{text:"Provider", placeholder:"spotify"},
|
||||
"music.playlist":{text:"Playlist url", placeholder:"https://embed.music.apple.com/en/playlist/"},
|
||||
"music.limit":{text:"Limit", type:"number", min:1, max:100},
|
||||
"music.user":{text:"Username", placeholder:"(default to GitHub login)"},
|
||||
"posts.limit":{text:"Limit", type:"number", min:1, max:30},
|
||||
"posts.user":{text:"Username", placeholder:"(default to GitHub login)"},
|
||||
"posts.source":{text:"Source", type:"select", values:["dev.to"]},
|
||||
"isocalendar.duration":{text:"Duration", type:"select", values:["half-year", "full-year"]},
|
||||
"projects.limit":{text:"Limit", type:"number", min:0, max:100},
|
||||
"projects.repositories":{text:"Repositories projects", placeholder:"user/repo/projects/1, ..."},
|
||||
"projects.descriptions":{text:"Projects descriptions", type:"boolean"},
|
||||
"topics.mode":{text:"Mode", type:"select", values:["starred", "mastered"]},
|
||||
"topics.sort":{text:"Sort by", type:"select", values:["starred", "activity", "stars", "random"]},
|
||||
"topics.limit":{text:"Limit", type:"number", min:0, max:20},
|
||||
"tweets.limit":{text:"Limit", type:"number", min:1, max:10},
|
||||
"tweets.user":{text:"Username", placeholder:"(default to GitHub attached)"},
|
||||
"stars.limit":{text:"Limit", type:"number", min:1, max:100},
|
||||
"activity.limit":{text:"Limit", type:"number", min:1, max:100},
|
||||
"activity.days":{text:"Max events age", type:"number", min:1, max:9999},
|
||||
"activity.filter":{text:"Events type", placeholder:"all"},
|
||||
"people.size":{text:"Limit", type:"number", min:16, max:64},
|
||||
"people.limit":{text:"Limit", type:"number", min:1, max:9999},
|
||||
"people.types":{text:"Types", placeholder:"followers, following"},
|
||||
"people.thanks":{text:"Special thanks", placeholder:"user1, user2, ..."},
|
||||
"people.identicons":{text:"Use identicons", type:"boolean"},
|
||||
"anilist.medias":{text:"Medias to display", placeholder:"anime, manga"},
|
||||
"anilist.sections":{text:"Sections to display", placeholder:"favorites, watching, reading, characters"},
|
||||
"anilist.limit":{text:"Limit", type:"number", min:0, max:9999},
|
||||
"anilist.shuffle":{text:"Shuffle data", type:"boolean"},
|
||||
"anilist.user":{text:"Username", placeholder:"(default to GitHub login)"},
|
||||
},
|
||||
"languages.ignored":"",
|
||||
"languages.skipped":"",
|
||||
"pagespeed.detailed":false,
|
||||
"pagespeed.screenshot":false,
|
||||
"habits.from":200,
|
||||
"habits.days":14,
|
||||
"habits.facts":true,
|
||||
"habits.charts":false,
|
||||
"music.provider":"",
|
||||
"music.playlist":"",
|
||||
"music.limit":4,
|
||||
"music.user":"",
|
||||
"posts.limit":4,
|
||||
"posts.user":"",
|
||||
"posts.source":"dev.to",
|
||||
"isocalendar.duration":"half-year",
|
||||
"projects.limit":4,
|
||||
"projects.repositories":"",
|
||||
"topics.mode":"starred",
|
||||
"topics.sort":"stars",
|
||||
"topics.limit":12,
|
||||
"tweets.limit":2,
|
||||
"tweets.user":"",
|
||||
"stars.limit":4,
|
||||
"activity.limit":5,
|
||||
"activity.days":14,
|
||||
"activity.filter":"all",
|
||||
"people.size":28,
|
||||
"people.limit":28,
|
||||
"people.types":"followers, following",
|
||||
"people.thanks":"",
|
||||
"people.identicons":false,
|
||||
"anilist.medias":"anime, manga",
|
||||
"anilist.sections":"favorites",
|
||||
"anilist.limit":2,
|
||||
"anilist.shuffle":true,
|
||||
"anilist.user":"",
|
||||
descriptions:{...(Object.assign({}, ...Object.entries(metadata).flatMap(([key, {web}]) => web)))},
|
||||
...(Object.fromEntries(Object.entries(
|
||||
Object.assign({}, ...Object.entries(metadata).flatMap(([key, {web}]) => web)))
|
||||
.map(([key, {defaulted}]) => [key, defaulted])
|
||||
))
|
||||
},
|
||||
},
|
||||
templates:{
|
||||
@@ -226,7 +136,7 @@
|
||||
`# Visit https://github.com/lowlighter/metrics/blob/master/action.yml for full reference`,
|
||||
`name: Metrics`,
|
||||
`on:`,
|
||||
` # Schedule updates`,
|
||||
` # Schedule updates (each hour)`,
|
||||
` schedule: [{cron: "0 * * * *"}]`,
|
||||
` # Lines below let you run workflow manually and on each commit`,
|
||||
` push: {branches: ["master", "main"]}`,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(function () {
|
||||
//Load asset
|
||||
(function ({axios, faker, ejs} = {axios:globalThis.axios, faker:globalThis.faker, ejs:globalThis.ejs}) {
|
||||
//Load assets
|
||||
const cached = new Map()
|
||||
async function load(url) {
|
||||
if (!cached.has(url))
|
||||
@@ -19,7 +19,7 @@
|
||||
return values.sort((a, b) => b - a)
|
||||
}
|
||||
//Placeholder function
|
||||
window.placeholder = async function (set) {
|
||||
globalThis.placeholder = async function (set) {
|
||||
//Load templates informations
|
||||
let {image, style, fonts, partials} = await load(`/.templates/${set.templates.selected}`)
|
||||
await Promise.all(partials.map(async partial => await load(`/.templates/${set.templates.selected}/partials/${partial}.ejs`)))
|
||||
@@ -78,6 +78,7 @@
|
||||
avatar:"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg=="
|
||||
},
|
||||
//User data
|
||||
account:"user",
|
||||
user:{
|
||||
databaseId:faker.random.number(10000000),
|
||||
name:"(placeholder)",
|
||||
@@ -127,10 +128,10 @@
|
||||
id:faker.random.number(100000000000000).toString(),
|
||||
created_at:faker.date.recent(),
|
||||
entities: {
|
||||
mentions: [ { start: 22, end: 33, username: 'lowlighter' } ]
|
||||
mentions: [ {start:22, end:33, username:"lowlighter"} ]
|
||||
},
|
||||
text: 'Checkout metrics from <span class="mention">@lowlighter</span> ! <span class="hashtag">#GitHub</span> ',
|
||||
mentions: [ 'lowlighter' ]
|
||||
mentions: ["lowlighter"]
|
||||
},
|
||||
...new Array(Number(options["tweets.limit"])-1).fill(null).map(_ => ({
|
||||
id:faker.random.number(100000000000000).toString(),
|
||||
@@ -590,4 +591,10 @@
|
||||
//Render
|
||||
return await ejs.render(image, data, {async:true, rmWhitespace:true})
|
||||
}
|
||||
//Reset globals contexts
|
||||
globalThis.placeholder.init = function(globals) {
|
||||
axios = globals.axios || axios
|
||||
faker = globals.faker || faker
|
||||
ejs = globals.ejs || ejs
|
||||
}
|
||||
})()
|
||||
|
||||
Reference in New Issue
Block a user