refactor(app/web): new features (#1124) [skip ci]
2
.github/config/label.yml
vendored
@@ -2,7 +2,7 @@
|
|||||||
- source/app/action/**
|
- source/app/action/**
|
||||||
- source/app/web/**
|
- source/app/web/**
|
||||||
✨ metrics insights:
|
✨ metrics insights:
|
||||||
- source/app/web/statics/about/**
|
- source/app/web/statics/insights/**
|
||||||
|
|
||||||
🧩 plugins:
|
🧩 plugins:
|
||||||
- source/plugins/**
|
- source/plugins/**
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ Generate metrics that can be embedded everywhere, including your GitHub profile
|
|||||||
<th colspan="2"><h2>🦑 Try it now!</h2></th>
|
<th colspan="2"><h2>🦑 Try it now!</h2></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><a href="https://metrics.lecoq.io">📊 Metrics embed</a></th>
|
<th><a href="https://metrics.lecoq.io/embed">📊 Metrics embed</a></th>
|
||||||
<th><a href="https://metrics.lecoq.io/about">✨ Metrics insights</a></th>
|
<th><a href="https://metrics.lecoq.io/insights">✨ Metrics insights</a></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
|
|||||||
@@ -220,7 +220,8 @@ export default async function(
|
|||||||
},
|
},
|
||||||
//Settings and tokens
|
//Settings and tokens
|
||||||
{
|
{
|
||||||
enabled = false
|
enabled = false,
|
||||||
|
extras = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
@@ -241,7 +242,7 @@ export default async function(
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
17
.github/scripts/preview.mjs
vendored
@@ -9,14 +9,12 @@ const __metrics = paths.join(paths.dirname(url.fileURLToPath(import.meta.url)),
|
|||||||
const __templates = paths.join(paths.join(__metrics, "source/templates/"))
|
const __templates = paths.join(paths.join(__metrics, "source/templates/"))
|
||||||
const __node_modules = paths.join(paths.join(__metrics, "node_modules"))
|
const __node_modules = paths.join(paths.join(__metrics, "node_modules"))
|
||||||
const __web = paths.join(paths.join(__metrics, "source/app/web/statics"))
|
const __web = paths.join(paths.join(__metrics, "source/app/web/statics"))
|
||||||
const __web_about = paths.join(paths.join(__web, "about"))
|
|
||||||
|
|
||||||
const __preview = paths.join(paths.join(__web, "preview"))
|
const __preview = paths.join(paths.join(__web, "preview"))
|
||||||
const __preview_js = paths.join(__preview, ".js")
|
const __preview_js = paths.join(__preview, ".js")
|
||||||
const __preview_css = paths.join(__preview, ".css")
|
const __preview_css = paths.join(__preview, ".css")
|
||||||
const __preview_templates = paths.join(__preview, ".templates")
|
const __preview_templates = paths.join(__preview, ".templates")
|
||||||
const __preview_templates_ = paths.join(__preview, ".templates_")
|
const __preview_templates_ = paths.join(__preview, ".templates_")
|
||||||
const __preview_about = paths.join(__preview, "about/.statics")
|
|
||||||
|
|
||||||
//Extract from web server
|
//Extract from web server
|
||||||
const {conf, Templates} = await setup({log: false})
|
const {conf, Templates} = await setup({log: false})
|
||||||
@@ -34,7 +32,6 @@ await fs.mkdir(__preview_js, {recursive: true})
|
|||||||
await fs.mkdir(__preview_css, {recursive: true})
|
await fs.mkdir(__preview_css, {recursive: true})
|
||||||
await fs.mkdir(__preview_templates, {recursive: true})
|
await fs.mkdir(__preview_templates, {recursive: true})
|
||||||
await fs.mkdir(__preview_templates_, {recursive: true})
|
await fs.mkdir(__preview_templates_, {recursive: true})
|
||||||
await fs.mkdir(__preview_about, {recursive: true})
|
|
||||||
|
|
||||||
//Web
|
//Web
|
||||||
fs.copyFile(paths.join(__web, "index.html"), paths.join(__preview, "index.html"))
|
fs.copyFile(paths.join(__web, "index.html"), paths.join(__preview, "index.html"))
|
||||||
@@ -81,9 +78,15 @@ fs.copyFile(paths.join(__node_modules, "clipboard/dist/clipboard.min.js"), paths
|
|||||||
//Meta
|
//Meta
|
||||||
fs.writeFile(paths.join(__preview, ".version"), JSON.stringify(`${conf.package.version}-preview`))
|
fs.writeFile(paths.join(__preview, ".version"), JSON.stringify(`${conf.package.version}-preview`))
|
||||||
fs.writeFile(paths.join(__preview, ".hosted"), JSON.stringify({by: "metrics", link: "https://github.com/lowlighter/metrics"}))
|
fs.writeFile(paths.join(__preview, ".hosted"), JSON.stringify({by: "metrics", link: "https://github.com/lowlighter/metrics"}))
|
||||||
//About
|
//Insights
|
||||||
fs.copyFile(paths.join(__web, "about", "index.html"), paths.join(__preview, "about", "index.html"))
|
for (const insight of ["insights", "about"]) {
|
||||||
for (const file of await fs.readdir(__web_about)) {
|
const __web_insights = paths.join(paths.join(__web, insight))
|
||||||
|
const __preview_insights = paths.join(__preview, `${insight}/.statics`)
|
||||||
|
await fs.mkdir(__preview_insights, {recursive: true})
|
||||||
|
|
||||||
|
fs.copyFile(paths.join(__web, insight, "index.html"), paths.join(__preview, insight, "index.html"))
|
||||||
|
for (const file of await fs.readdir(__web_insights)) {
|
||||||
if (file !== ".statics")
|
if (file !== ".statics")
|
||||||
fs.copyFile(paths.join(__web_about, file), paths.join(__preview_about, file))
|
fs.copyFile(paths.join(__web_insights, file), paths.join(__preview_insights, file))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
6
.github/scripts/quickstart/plugin/index.mjs
vendored
@@ -1,15 +1,15 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, computed, rest, graphql, queries, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, computed, rest, graphql, queries, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled)||(!q.<%= name %>))
|
if ((!enabled) || (!q.<%= name %>) || (!imports.metadata.plugins.<%= name %>.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
//Results
|
//Results
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
|||||||
//Initialization
|
//Initialization
|
||||||
const pending = []
|
const pending = []
|
||||||
const {queries} = conf
|
const {queries} = conf
|
||||||
const extras = {css: (conf.settings.extras?.css ?? conf.settings.extras?.default) ? q["extras.css"] ?? "" : "", js: (conf.settings.extras?.js ?? conf.settings.extras?.default) ? q["extras.js"] ?? "" : ""}
|
const extras = {css: imports.metadata.plugins.core.extras("extras_css", {...conf.settings, error:false}) ? q["extras.css"] ?? "" : "", js: imports.metadata.plugins.core.extras("extras_js", {...conf.settings, error:false}) ? q["extras.js"] ?? "" : ""}
|
||||||
const data = {q, animated: true, large: false, base: {}, config: {}, errors: [], plugins: {}, computed: {}, extras, postscripts: []}
|
const data = {q, animated: true, large: false, base: {}, config: {}, errors: [], plugins: {}, computed: {}, extras, postscripts: []}
|
||||||
const imports = {
|
const imports = {
|
||||||
plugins: Plugins,
|
plugins: Plugins,
|
||||||
@@ -184,7 +184,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
|||||||
if ((conf.settings?.optimize === true) || (conf.settings?.optimize?.includes?.("svg")))
|
if ((conf.settings?.optimize === true) || (conf.settings?.optimize?.includes?.("svg")))
|
||||||
rendered = await imports.svg.optimize.svg(rendered, q, experimental)
|
rendered = await imports.svg.optimize.svg(rendered, q, experimental)
|
||||||
//Verify svg
|
//Verify svg
|
||||||
if (verify) {
|
if ((verify)&&(imports.metadata.plugins.core.extras("verify", {...conf.settings, error:false}))) {
|
||||||
console.debug(`metrics/compute/${login} > verify SVG`)
|
console.debug(`metrics/compute/${login} > verify SVG`)
|
||||||
let libxmljs = null
|
let libxmljs = null
|
||||||
try {
|
try {
|
||||||
@@ -281,9 +281,9 @@ metrics.insights.output = async function({login, imports, conf}, {graphql, rest,
|
|||||||
console.debug(`metrics/compute/${login} > insights > generating data`)
|
console.debug(`metrics/compute/${login} > insights > generating data`)
|
||||||
const result = await metrics.insights({login}, {graphql, rest, conf}, {Plugins, Templates})
|
const result = await metrics.insights({login}, {graphql, rest, conf}, {Plugins, Templates})
|
||||||
const json = JSON.stringify(result)
|
const json = JSON.stringify(result)
|
||||||
await page.goto(`${server}/about/${login}?embed=1&localstorage=1`)
|
await page.goto(`${server}/insights/${login}?embed=1&localstorage=1`)
|
||||||
await page.evaluate(async json => localStorage.setItem("local.metrics", json), json) //eslint-disable-line no-undef
|
await page.evaluate(async json => localStorage.setItem("local.metrics", json), json) //eslint-disable-line no-undef
|
||||||
await page.goto(`${server}/about/${login}?embed=1&localstorage=1`)
|
await page.goto(`${server}/insights/${login}?embed=1&localstorage=1`)
|
||||||
await page.waitForSelector(".container .user", {timeout: 10 * 60 * 1000})
|
await page.waitForSelector(".container .user", {timeout: 10 * 60 * 1000})
|
||||||
|
|
||||||
//Rendering
|
//Rendering
|
||||||
@@ -297,7 +297,7 @@ metrics.insights.output = async function({login, imports, conf}, {graphql, rest,
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
${await page.evaluate(() => document.querySelector("main").outerHTML)}
|
${await page.evaluate(() => document.querySelector("main").outerHTML)}
|
||||||
${(await Promise.all([".css/style.vars.css", ".css/style.css", "about/.statics/style.css"].map(path => utils.axios.get(`${server}/${path}`)))).map(({data: style}) => `<style>${style}</style>`).join("\n")}
|
${(await Promise.all([".css/style.vars.css", ".css/style.css", "insights/.statics/style.css"].map(path => utils.axios.get(`${server}/${path}`)))).map(({data: style}) => `<style>${style}</style>`).join("\n")}
|
||||||
</body>
|
</body>
|
||||||
</html>`
|
</html>`
|
||||||
await browser.close()
|
await browser.close()
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
|||||||
if (account !== "bypass") {
|
if (account !== "bypass") {
|
||||||
const context = q.repo ? "repository" : account
|
const context = q.repo ? "repository" : account
|
||||||
if (!meta.supports?.includes(context))
|
if (!meta.supports?.includes(context))
|
||||||
throw {error: {message: `Not supported for: ${context}`, instance: new Error()}}
|
throw {error: {message: `Unsupported context ${context}`, instance: new Error()}}
|
||||||
}
|
}
|
||||||
//Special values replacer
|
//Special values replacer
|
||||||
const replacer = value => {
|
const replacer = value => {
|
||||||
@@ -214,34 +214,60 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
|||||||
|
|
||||||
//Extra features parser
|
//Extra features parser
|
||||||
{
|
{
|
||||||
meta.extras = function(input, {extras = {}}) {
|
meta.extras = function(input, {extras = {}, error = true}) {
|
||||||
|
const key = metadata.to.yaml(input, {name})
|
||||||
|
try {
|
||||||
//Required permissions
|
//Required permissions
|
||||||
const required = inputs[metadata.to.yaml(input, {name})]?.extras ?? null
|
const required = inputs[key]?.extras ?? null
|
||||||
if (!required)
|
if (!required)
|
||||||
return true
|
return true
|
||||||
console.debug(`metrics/extras > ${name} > ${input} > require [${required}]`)
|
console.debug(`metrics/extras > ${name} > ${key} > require [${required}]`)
|
||||||
|
|
||||||
//Legacy handling
|
//Legacy handling
|
||||||
const enabled = extras?.features ?? extras?.default ?? false
|
const enabled = extras?.features ?? extras?.default ?? false
|
||||||
if (typeof enabled === "boolean") {
|
if (typeof enabled === "boolean") {
|
||||||
console.debug(`metrics/extras > ${name} > ${input} > extras features is set to ${enabled}`)
|
console.debug(`metrics/extras > ${name} > ${key} > extras features is set to ${enabled}`)
|
||||||
|
if (!enabled)
|
||||||
|
throw new Error()
|
||||||
return enabled
|
return enabled
|
||||||
}
|
}
|
||||||
if (!Array.isArray(required)) {
|
if (!Array.isArray(required)) {
|
||||||
console.debug(`metrics/extras > ${name} > ${input} > extras is not a permission array, skipping`)
|
console.debug(`metrics/extras > ${name} > ${key} > extras is not a permission array, skipping`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check permissions
|
//Legacy options handling
|
||||||
if (!Array.isArray(extras.features))
|
if (!Array.isArray(extras.features))
|
||||||
throw new Error(`metrics/extras > ${name} > ${input} > extras.features is not an array`)
|
throw new Error(`metrics/extras > ${name} > ${key} > extras.features is not an array`)
|
||||||
|
if (extras.css) {
|
||||||
|
console.warn(`metrics/extras > ${name} > ${key} > extras.css is deprecated, use extras.features with "metrics.run.puppeteer.user.css" instead`)
|
||||||
|
extras.features.push("metrics.run.puppeteer.user.css")
|
||||||
|
}
|
||||||
|
if (extras.js) {
|
||||||
|
console.warn(`metrics/extras > ${name} > ${key} > extras.js is deprecated, use extras.features with "metrics.run.puppeteer.user.js" instead`)
|
||||||
|
extras.features.push("metrics.run.puppeteer.user.js")
|
||||||
|
}
|
||||||
|
if (extras.presets) {
|
||||||
|
console.warn(`metrics/extras > ${name} > ${key} > extras.presets is deprecated, use extras.features with "metrics.setup.community.presets" instead`)
|
||||||
|
extras.features.push("metrics.setup.community.presets")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check permissions
|
||||||
const missing = required.filter(permission => !extras.features.includes(permission))
|
const missing = required.filter(permission => !extras.features.includes(permission))
|
||||||
if (missing.length > 0) {
|
if (missing.length > 0) {
|
||||||
console.debug(`metrics/extras > ${name} > ${input} > missing permissions [${missing}], skipping`)
|
console.debug(`metrics/extras > ${name} > ${key} > missing permissions [${missing}]`)
|
||||||
return false
|
throw new Error()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
catch {
|
||||||
|
if (!error) {
|
||||||
|
console.debug(`metrics/extras > ${name} > ${key} > skipping (no error mode)`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
throw Object.assign(new Error(`Unsupported option "${key}"`), {extras:true})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Action metadata
|
//Action metadata
|
||||||
@@ -576,7 +602,9 @@ metadata.to = {
|
|||||||
return name ? key.replace(new RegExp(`^(${name}.)`, "g"), "") : key
|
return name ? key.replace(new RegExp(`^(${name}.)`, "g"), "") : key
|
||||||
},
|
},
|
||||||
yaml(key, {name = ""} = {}) {
|
yaml(key, {name = ""} = {}) {
|
||||||
const parts = [key.replaceAll(".", "_")]
|
const parts = []
|
||||||
|
if (key !== "enabled")
|
||||||
|
parts.unshift(key.replaceAll(".", "_"))
|
||||||
if (name)
|
if (name)
|
||||||
parts.unshift((name === "base") ? name : `plugin_${name}`)
|
parts.unshift((name === "base") ? name : `plugin_${name}`)
|
||||||
return parts.join("_")
|
return parts.join("_")
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
|||||||
logger("metrics/setup > load package.json > success")
|
logger("metrics/setup > load package.json > success")
|
||||||
|
|
||||||
//Load community templates
|
//Load community templates
|
||||||
|
if ((conf.settings.extras?.features?.includes("metrics.setup.community.templates"))||(conf.settings.extras?.features === true)||(conf.settings.extras?.default)) {
|
||||||
if ((typeof conf.settings.community.templates === "string") && (conf.settings.community.templates.length)) {
|
if ((typeof conf.settings.community.templates === "string") && (conf.settings.community.templates.length)) {
|
||||||
logger("metrics/setup > parsing community templates list")
|
logger("metrics/setup > parsing community templates list")
|
||||||
conf.settings.community.templates = [...new Set([...decodeURIComponent(conf.settings.community.templates).split(",").map(v => v.trim().toLocaleLowerCase()).filter(v => v)])]
|
conf.settings.community.templates = [...new Set([...decodeURIComponent(conf.settings.community.templates).split(",").map(v => v.trim().toLocaleLowerCase()).filter(v => v)])]
|
||||||
@@ -136,6 +137,10 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
|||||||
else {
|
else {
|
||||||
logger("metrics/setup > no community templates to install")
|
logger("metrics/setup > no community templates to install")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger("metrics/setup > community templates are disabled")
|
||||||
|
}
|
||||||
|
|
||||||
//Load templates
|
//Load templates
|
||||||
for (const name of await fs.promises.readdir(__templates)) {
|
for (const name of await fs.promises.readdir(__templates)) {
|
||||||
@@ -188,6 +193,18 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
|||||||
//Load metadata
|
//Load metadata
|
||||||
conf.metadata = await metadata({log})
|
conf.metadata = await metadata({log})
|
||||||
|
|
||||||
|
//Modes
|
||||||
|
if ((!conf.settings.modes)||(!conf.settings.modes.length))
|
||||||
|
conf.settings.modes = ["embed", "insights"]
|
||||||
|
logger(`metrics/setup > setup > enabled modes ${JSON.stringify(conf.settings.modes)}`)
|
||||||
|
|
||||||
|
//Allowed outputs formats
|
||||||
|
if ((!conf.settings.outputs)||(!conf.settings.outputs.length))
|
||||||
|
conf.settings.outputs = metadata.inputs.config_output.values
|
||||||
|
else
|
||||||
|
conf.settings.outputs = conf.settings.outputs.filter(format => metadata.inputs.config_output.values.includes(format))
|
||||||
|
logger(`metrics/setup > setup > allowed outputs ${JSON.stringify(conf.settings.outputs)}`)
|
||||||
|
|
||||||
//Store authenticated user
|
//Store authenticated user
|
||||||
if (conf.settings.token) {
|
if (conf.settings.token) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -125,6 +125,49 @@ export function formatters({timeZone} = {}) {
|
|||||||
return license.nickname ?? license.spdxId ?? license.name
|
return license.nickname ?? license.spdxId ?? license.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**Error formatter */
|
||||||
|
format.error = function(error, {descriptions = {}, ...attributes} = {}) {
|
||||||
|
try {
|
||||||
|
//Extras features error
|
||||||
|
if (error.extras)
|
||||||
|
throw {error: {message: error.message, instance: error}}
|
||||||
|
//Already formatted error
|
||||||
|
if (error.error?.message)
|
||||||
|
throw error
|
||||||
|
//Custom description
|
||||||
|
let message = "Unexpected error"
|
||||||
|
if (descriptions.custom) {
|
||||||
|
const description = descriptions.custom(error)
|
||||||
|
if (description)
|
||||||
|
message += ` (${description})`
|
||||||
|
}
|
||||||
|
//Axios error
|
||||||
|
if (error.isAxiosError) {
|
||||||
|
//Error code
|
||||||
|
const status = error.response?.status
|
||||||
|
message = `API error: ${status}`
|
||||||
|
|
||||||
|
//Error description (optional)
|
||||||
|
if ((descriptions)&&(descriptions[status]))
|
||||||
|
message += ` (${descriptions[status]})`
|
||||||
|
else {
|
||||||
|
const description = error.response?.data?.errors?.[0]?.message ?? error.response.data?.error_description ?? error.response?.data?.message ?? null
|
||||||
|
if (description)
|
||||||
|
message += ` (${description})`
|
||||||
|
}
|
||||||
|
|
||||||
|
//Error data
|
||||||
|
console.debug(error.response.data)
|
||||||
|
error = error.response?.data ?? null
|
||||||
|
throw {error: {message, instance: error}}
|
||||||
|
}
|
||||||
|
throw {error: {message, instance: error}}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return Object.assign(error, attributes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {format}
|
return {format}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,15 +130,12 @@ export default async function({sandbox = false} = {}) {
|
|||||||
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))
|
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)
|
for (const template in conf.templates)
|
||||||
app.use(`/.templates/${template}/partials`, express.static(`${conf.paths.templates}/${template}/partials`))
|
app.use(`/.templates/${template}/partials`, express.static(`${conf.paths.templates}/${template}/partials`))
|
||||||
//Placeholders
|
|
||||||
app.use("/.placeholders", express.static(`${conf.paths.statics}/placeholders`))
|
|
||||||
//Styles
|
//Styles
|
||||||
app.get("/.css/style.css", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/style.css`))
|
app.get("/.css/style.css", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/style.css`))
|
||||||
app.get("/.css/style.vars.css", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/style.vars.css`))
|
app.get("/.css/style.vars.css", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/style.vars.css`))
|
||||||
app.get("/.css/style.prism.css", limiter, (req, res) => res.sendFile(`${conf.paths.node_modules}/prismjs/themes/prism-tomorrow.css`))
|
app.get("/.css/style.prism.css", limiter, (req, res) => res.sendFile(`${conf.paths.node_modules}/prismjs/themes/prism-tomorrow.css`))
|
||||||
//Scripts
|
//Scripts
|
||||||
app.get("/.js/app.js", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/app.js`))
|
app.get("/.js/app.js", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/app.js`))
|
||||||
app.get("/.js/app.placeholder.js", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/app.placeholder.js`))
|
|
||||||
app.get("/.js/ejs.min.js", limiter, (req, res) => res.sendFile(`${conf.paths.node_modules}/ejs/ejs.min.js`))
|
app.get("/.js/ejs.min.js", limiter, (req, res) => res.sendFile(`${conf.paths.node_modules}/ejs/ejs.min.js`))
|
||||||
app.get("/.js/faker.min.js", limiter, (req, res) => res.set({"Content-Type": "text/javascript"}).send("import {faker} from '/.js/faker/index.mjs';globalThis.faker=faker;globalThis.placeholder.init(globalThis)"))
|
app.get("/.js/faker.min.js", limiter, (req, res) => res.set({"Content-Type": "text/javascript"}).send("import {faker} from '/.js/faker/index.mjs';globalThis.faker=faker;globalThis.placeholder.init(globalThis)"))
|
||||||
app.use("/.js/faker", express.static(`${conf.paths.node_modules}/@faker-js/faker/dist/esm`))
|
app.use("/.js/faker", express.static(`${conf.paths.node_modules}/@faker-js/faker/dist/esm`))
|
||||||
@@ -176,12 +173,18 @@ export default async function({sandbox = false} = {}) {
|
|||||||
//Pending requests
|
//Pending requests
|
||||||
const pending = new Map()
|
const pending = new Map()
|
||||||
|
|
||||||
//About routes
|
//Metrics insights
|
||||||
app.use("/about/.statics/", express.static(`${conf.paths.statics}/about`))
|
if (conf.settings.modes.includes("insights")) {
|
||||||
app.get("/about/", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/about/index.html`))
|
console.debug("metrics/app > setup insights mode")
|
||||||
app.get("/about/index.html", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/about/index.html`))
|
//Legacy routes
|
||||||
app.get("/about/:login", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/about/index.html`))
|
app.get("/about/*", (req, res) => res.redirect(req.path.replace("/about/", "/insights/")))
|
||||||
app.get("/about/query/:login/:plugin/", async (req, res) => {
|
//Static routes
|
||||||
|
app.get("/insights/", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/insights/index.html`))
|
||||||
|
app.get("/insights/index.html", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/insights/index.html`))
|
||||||
|
app.use("/insights/.statics/", express.static(`${conf.paths.statics}/insights`))
|
||||||
|
//App routes
|
||||||
|
app.get("/insights/:login", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/insights/index.html`))
|
||||||
|
app.get("/insights/query/:login/:plugin/", async (req, res) => {
|
||||||
//Check username
|
//Check username
|
||||||
const login = req.params.login?.replace(/[\n\r]/g, "")
|
const login = req.params.login?.replace(/[\n\r]/g, "")
|
||||||
if (!/^[-\w]+$/i.test(login)) {
|
if (!/^[-\w]+$/i.test(login)) {
|
||||||
@@ -194,11 +197,11 @@ export default async function({sandbox = false} = {}) {
|
|||||||
console.debug(`metrics/app/${login}/insights > 400 (invalid plugin name)`)
|
console.debug(`metrics/app/${login}/insights > 400 (invalid plugin name)`)
|
||||||
return res.status(400).send("Bad request: plugin name seems invalid")
|
return res.status(400).send("Bad request: plugin name seems invalid")
|
||||||
}
|
}
|
||||||
if (cache.get(`about.${login}.${plugin}`))
|
if (cache.get(`insights.${login}.${plugin}`))
|
||||||
return res.send(cache.get(`about.${login}.${plugin}`))
|
return res.send(cache.get(`insights.${login}.${plugin}`))
|
||||||
return res.status(204).send("No content: no data fetched yet")
|
return res.status(204).send("No content: no data fetched yet")
|
||||||
})
|
})
|
||||||
app.get("/about/query/:login/", ...middlewares, async (req, res) => {
|
app.get("/insights/query/:login/", ...middlewares, async (req, res) => {
|
||||||
//Check username
|
//Check username
|
||||||
const login = req.params.login?.replace(/[\n\r]/g, "")
|
const login = req.params.login?.replace(/[\n\r]/g, "")
|
||||||
if (!/^[-\w]+$/i.test(login)) {
|
if (!/^[-\w]+$/i.test(login)) {
|
||||||
@@ -209,34 +212,34 @@ export default async function({sandbox = false} = {}) {
|
|||||||
let solve = null
|
let solve = null
|
||||||
try {
|
try {
|
||||||
//Prevent multiples requests
|
//Prevent multiples requests
|
||||||
if ((!debug) && (!mock) && (pending.has(`about.${login}`))) {
|
if ((!debug) && (!mock) && (pending.has(`insights.${login}`))) {
|
||||||
console.debug(`metrics/app/${login}/insights > awaiting pending request`)
|
console.debug(`metrics/app/${login}/insights > awaiting pending request`)
|
||||||
await pending.get(`about.${login}`)
|
await pending.get(`insights.${login}`)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pending.set(`about.${login}`, new Promise(_solve => solve = _solve))
|
pending.set(`insights.${login}`, new Promise(_solve => solve = _solve))
|
||||||
}
|
}
|
||||||
//Read cached data if possible
|
//Read cached data if possible
|
||||||
if ((!debug) && (cached) && (cache.get(`about.${login}`))) {
|
if ((!debug) && (cached) && (cache.get(`insights.${login}`))) {
|
||||||
console.debug(`metrics/app/${login}/insights > using cached results`)
|
console.debug(`metrics/app/${login}/insights > using cached results`)
|
||||||
return res.send(cache.get(`about.${login}`))
|
return res.send(cache.get(`insights.${login}`))
|
||||||
}
|
}
|
||||||
//Compute metrics
|
//Compute metrics
|
||||||
console.debug(`metrics/app/${login}/insights > compute insights`)
|
console.debug(`metrics/app/${login}/insights > compute insights`)
|
||||||
const callbacks = {
|
const callbacks = {
|
||||||
async plugin(login, plugin, success, result) {
|
async plugin(login, plugin, success, result) {
|
||||||
console.debug(`metrics/app/${login}/insights/plugins > ${plugin} > ${success ? "success" : "failure"}`)
|
console.debug(`metrics/app/${login}/insights/plugins > ${plugin} > ${success ? "success" : "failure"}`)
|
||||||
cache.put(`about.${login}.${plugin}`, result)
|
cache.put(`insights.${login}.${plugin}`, result)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
const json = await metrics.insights({login}, {graphql, rest, conf, callbacks}, {Plugins, Templates})
|
const json = await metrics.insights({login}, {graphql, rest, conf, callbacks}, {Plugins, Templates})
|
||||||
//Cache
|
//Cache
|
||||||
cache.put(`about.${login}`, json)
|
cache.put(`insights.${login}`, json)
|
||||||
if ((!debug) && (cached)) {
|
if ((!debug) && (cached)) {
|
||||||
const maxage = Math.round(Number(req.query.cache))
|
const maxage = Math.round(Number(req.query.cache))
|
||||||
cache.put(`about.${login}`, json, maxage > 0 ? maxage : cached)
|
cache.put(`insights.${login}`, json, maxage > 0 ? maxage : cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -268,8 +271,22 @@ export default async function({sandbox = false} = {}) {
|
|||||||
_requests_refresh = true
|
_requests_refresh = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
app.get("/about/*", (req, res) => res.redirect(req.path.replace("/about/", "/insights/")))
|
||||||
|
app.get("/insights/*", (req, res) => res.status(405).send("Method not allowed: this endpoint is not available"))
|
||||||
|
}
|
||||||
|
|
||||||
//Metrics
|
//Metrics embed
|
||||||
|
if (conf.settings.modes.includes("embed")) {
|
||||||
|
console.debug("metrics/app > setup embed mode")
|
||||||
|
//Static routes
|
||||||
|
app.get("/embed/", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/embed/index.html`))
|
||||||
|
app.get("/embed/index.html", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/embed/index.html`))
|
||||||
|
app.use("/.placeholders", express.static(`${conf.paths.statics}/embed/placeholders`))
|
||||||
|
app.get("/.js/embed/app.js", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/embed/app.js`))
|
||||||
|
app.get("/.js/embed/app.placeholder.js", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/embed/app.placeholder.js`))
|
||||||
|
//App routes
|
||||||
app.get("/:login/:repository?", ...middlewares, async (req, res) => {
|
app.get("/:login/:repository?", ...middlewares, async (req, res) => {
|
||||||
//Request params
|
//Request params
|
||||||
const login = req.params.login?.replace(/[\n\r]/g, "")
|
const login = req.params.login?.replace(/[\n\r]/g, "")
|
||||||
@@ -319,10 +336,11 @@ export default async function({sandbox = false} = {}) {
|
|||||||
//Render
|
//Render
|
||||||
const q = req.query
|
const q = req.query
|
||||||
console.debug(`metrics/app/${login} > ${util.inspect(q, {depth: Infinity, maxStringLength: 256})}`)
|
console.debug(`metrics/app/${login} > ${util.inspect(q, {depth: Infinity, maxStringLength: 256})}`)
|
||||||
if ((q["config.presets"]) && (conf.settings.extras?.presets ?? conf.settings.extras?.default ?? false)) {
|
if ((q["config.presets"]) && ((conf.settings.extras?.features?.includes("metrics.setup.community.presets"))||(conf.settings.extras?.features === true)||(conf.settings.extras?.default))) {
|
||||||
console.debug(`metrics/app/${login} > presets have been specified, loading them`)
|
console.debug(`metrics/app/${login} > presets have been specified, loading them`)
|
||||||
Object.assign(q, await presets(q["config.presets"]))
|
Object.assign(q, await presets(q["config.presets"]))
|
||||||
}
|
}
|
||||||
|
const convert = conf.settings.outputs.includes(q["config.output"]) ? q["config.output"] : conf.settings.outputs[0]
|
||||||
const {rendered, mime} = await metrics({login, q}, {
|
const {rendered, mime} = await metrics({login, q}, {
|
||||||
graphql,
|
graphql,
|
||||||
rest,
|
rest,
|
||||||
@@ -330,7 +348,7 @@ export default async function({sandbox = false} = {}) {
|
|||||||
conf,
|
conf,
|
||||||
die: q["plugins.errors.fatal"] ?? false,
|
die: q["plugins.errors.fatal"] ?? false,
|
||||||
verify: q.verify ?? false,
|
verify: q.verify ?? false,
|
||||||
convert: ["svg", "jpeg", "png", "json", "markdown", "markdown-pdf", "insights"].includes(q["config.output"]) ? q["config.output"] : null,
|
convert: convert !== "auto" ? convert : null,
|
||||||
}, {Plugins, Templates})
|
}, {Plugins, Templates})
|
||||||
//Cache
|
//Cache
|
||||||
if ((!debug) && (cached)) {
|
if ((!debug) && (cached)) {
|
||||||
@@ -374,19 +392,39 @@ export default async function({sandbox = false} = {}) {
|
|||||||
_requests_refresh = true
|
_requests_refresh = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
app.get("/embed/*", (req, res) => res.status(405).send("Method not allowed: this endpoint is not available"))
|
||||||
|
}
|
||||||
|
|
||||||
//Listen
|
//Listen
|
||||||
app.listen(port, () =>
|
app.listen(port, () =>
|
||||||
console.log([
|
console.log([
|
||||||
|
"───────────────────────────────────────────────────────────────────",
|
||||||
|
"── Server configuration ───────────────────────────────────────────",
|
||||||
`Listening on port │ ${port}`,
|
`Listening on port │ ${port}`,
|
||||||
`Debug mode │ ${debug}`,
|
`Modes │ ${conf.settings.modes}`,
|
||||||
`Mocked data │ ${conf.settings.mocked ?? false}`,
|
"── Server capacity ───────────────────────────────────────────────",
|
||||||
`Restricted to users │ ${restricted.size ? [...restricted].join(", ") : "(unrestricted)"}`,
|
`Restricted to users │ ${restricted.size ? [...restricted].join(", ") : "(unrestricted)"}`,
|
||||||
`Cached time │ ${cached} seconds`,
|
|
||||||
`Rate limiter │ ${ratelimiter ? util.inspect(ratelimiter, {depth: Infinity, maxStringLength: 256}) : "(enabled)"}`,
|
|
||||||
`Max simultaneous users │ ${maxusers ? `${maxusers} users` : "(unrestricted)"}`,
|
`Max simultaneous users │ ${maxusers ? `${maxusers} users` : "(unrestricted)"}`,
|
||||||
`Plugins enabled │ ${enabled.map(({name}) => name).join(", ")}`,
|
`Rate limiter │ ${ratelimiter ? util.inspect(ratelimiter, {depth: Infinity, maxStringLength: 256}) : "(enabled)"}`,
|
||||||
|
`Max repositories per user │ ${conf.settings.repositories}`,
|
||||||
|
"── Render settings ────────────────────────────────────────────────",
|
||||||
|
`Cached time │ ${cached} seconds`,
|
||||||
`SVG optimization │ ${conf.settings.optimize ?? false}`,
|
`SVG optimization │ ${conf.settings.optimize ?? false}`,
|
||||||
|
`Allowed outputs │ ${conf.settings.outputs.join(", ")}`,
|
||||||
|
`Padding │ ${conf.settings.padding}`,
|
||||||
|
"── Sandbox ────────────────────────────────────────────────────────",
|
||||||
|
`Debug │ ${debug}`,
|
||||||
|
`Debug (puppeteer) │ ${conf.settings["debug.headless"] ?? false}`,
|
||||||
|
`Mocked data │ ${conf.settings.mocked ?? false}`,
|
||||||
|
"── Content ────────────────────────────────────────────────────────",
|
||||||
|
`Plugins enabled │ ${enabled.map(({name}) => name).join(", ")}`,
|
||||||
|
`Templates enabled │ ${templates.filter(({enabled}) => enabled).map(({name}) => name).join(", ")}`,
|
||||||
|
"── Extras ─────────────────────────────────────────────────────────",
|
||||||
|
`Default │ ${conf.settings.extras.default ?? false}`,
|
||||||
|
`Features │ ${conf.settings.extras.features ?? "(none)"}`,
|
||||||
|
"───────────────────────────────────────────────────────────────────",
|
||||||
"Server ready !",
|
"Server ready !",
|
||||||
].join("\n")))
|
].join("\n")))
|
||||||
}
|
}
|
||||||
|
|||||||
23
source/app/web/settings.example.json
generated
@@ -2,7 +2,8 @@
|
|||||||
"//": "Example of configuration for metrics web instance",
|
"//": "Example of configuration for metrics web instance",
|
||||||
"//": "====================================================================",
|
"//": "====================================================================",
|
||||||
|
|
||||||
"token": "MY GITHUB API TOKEN", "//": "GitHub Personal Token (required)",
|
"token": "GITHUB API TOKEN", "//": "GitHub Personal Token (required)",
|
||||||
|
"modes": ["embed", "insights"], "//": "Web instance enabled modes",
|
||||||
"restricted": [], "//": "Authorized users (empty to disable)",
|
"restricted": [], "//": "Authorized users (empty to disable)",
|
||||||
"maxusers": 0, "//": "Maximum users, (0 to disable)",
|
"maxusers": 0, "//": "Maximum users, (0 to disable)",
|
||||||
"cached": 3600000, "//": "Cache time rendered metrics (0 to disable)",
|
"cached": 3600000, "//": "Cache time rendered metrics (0 to disable)",
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
"mocked": false, "//": "Use mocked data instead of live APIs (use 'force' to use mocked token even if real token are defined)",
|
"mocked": false, "//": "Use mocked data instead of live APIs (use 'force' to use mocked token even if real token are defined)",
|
||||||
"repositories": 100, "//": "Number of repositories to use",
|
"repositories": 100, "//": "Number of repositories to use",
|
||||||
"padding": ["0", "8 + 11%"], "//": "Image padding (default)",
|
"padding": ["0", "8 + 11%"], "//": "Image padding (default)",
|
||||||
|
"outputs": ["svg", "png", "json"], "//": "Image output formats (empty to enable all)",
|
||||||
"hosted": {
|
"hosted": {
|
||||||
"by": "", "//": "Web instance host (displayed in footer)",
|
"by": "", "//": "Web instance host (displayed in footer)",
|
||||||
"link": "", "//": "Web instance host link (displayed in footer)"
|
"link": "", "//": "Web instance host link (displayed in footer)"
|
||||||
@@ -28,9 +30,22 @@
|
|||||||
"extras": {
|
"extras": {
|
||||||
"default": false, "//": "Default extras state (advised to let 'false' unless in debug mode)",
|
"default": false, "//": "Default extras state (advised to let 'false' unless in debug mode)",
|
||||||
"presets": false, "//": "Allow use of 'config.presets' option",
|
"presets": false, "//": "Allow use of 'config.presets' option",
|
||||||
"css": false, "//": "Allow use of 'extras.css' option",
|
"features": false, "//": "Enable extra features (advised to let 'false' on web instances), see below for supported features",
|
||||||
"js": false, "//": "Allow use of 'extras.js' option",
|
"//": "________________________________________________________________________",
|
||||||
"features": false, "//": "Enable extra features (advised to let 'false' on web instances)"
|
"//": "metrics.setup.community.templates | Allow community templates download",
|
||||||
|
"//": "metrics.setup.community.presets | Allow community presets usage",
|
||||||
|
"//": "metrics.api.github.overuse | Allow GitHub API intensive requests",
|
||||||
|
"//": "metrics.cpu.overuse | Allow CPU intensive requests",
|
||||||
|
"//": "metrics.run.tempdir | Allow access to temporary directory (I/O operations may be performed)",
|
||||||
|
"//": "metrics.run.git | Allow to run git (needs to be installed)",
|
||||||
|
"//": "metrics.run.licensed | Allow to run licensed (needs to be installed)",
|
||||||
|
"//": "metrics.run.user.cmd | Allow to run ANY command by user (USE WITH CAUTION!)",
|
||||||
|
"//": "metrics.run.puppeteer.scrapping | Allow to run puppeteer to scrape data",
|
||||||
|
"//": "metrics.run.puppeteer.user.css | Allow to run CSS by user during puppeteer render",
|
||||||
|
"//": "metrics.run.puppeteer.user.js | Allow to run JavaScript by user during puppeteer render",
|
||||||
|
"//": "metrics.npm.optional.chartist | Allow use of chartist (needs to be installed)",
|
||||||
|
"//": "metrics.npm.optional.gifencoder | Allow use of gifencoder (needs to be installed)",
|
||||||
|
"//": "metrics.npm.optional.libxmljs2 | Allow use of libxmljs2 (needs to be installed)"
|
||||||
},
|
},
|
||||||
"plugins.default": false, "//": "Default plugin state (advised to let 'false' unless in debug mode)",
|
"plugins.default": false, "//": "Default plugin state (advised to let 'false' unless in debug mode)",
|
||||||
"plugins": { "//": "Global plugin configuration",
|
"plugins": { "//": "Global plugin configuration",
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
;(async function() {
|
;(async function() {
|
||||||
//Init
|
|
||||||
const {data: metadata} = await axios.get("/.plugins.metadata")
|
|
||||||
delete metadata.core.web.output
|
|
||||||
delete metadata.core.web.twemojis
|
|
||||||
//App
|
//App
|
||||||
return new Vue({
|
return new Vue({
|
||||||
//Initialization
|
//Initialization
|
||||||
@@ -62,7 +58,6 @@
|
|||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
},
|
},
|
||||||
components: {Prism: PrismComponent},
|
|
||||||
//Watchers
|
//Watchers
|
||||||
watch: {
|
watch: {
|
||||||
tab: {
|
tab: {
|
||||||
@@ -86,244 +81,20 @@
|
|||||||
data: {
|
data: {
|
||||||
version: "",
|
version: "",
|
||||||
user: "",
|
user: "",
|
||||||
mode: "metrics",
|
|
||||||
tab: "overview",
|
tab: "overview",
|
||||||
palette: "light",
|
palette: "light",
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
requests: {rest: {limit: 0, used: 0, remaining: 0, reset: NaN}, graphql: {limit: 0, used: 0, remaining: 0, reset: NaN}},
|
requests: {rest: {limit: 0, used: 0, remaining: 0, reset: NaN}, graphql: {limit: 0, used: 0, remaining: 0, reset: NaN}},
|
||||||
cached: new Map(),
|
cached: new Map(),
|
||||||
config: Object.fromEntries(Object.entries(metadata.core.web).map(([key, {defaulted}]) => [key, defaulted])),
|
|
||||||
metadata: Object.fromEntries(Object.entries(metadata).map(([key, {web}]) => [key, web])),
|
|
||||||
hosted: null,
|
hosted: null,
|
||||||
docs: {
|
|
||||||
overview: {
|
|
||||||
link: "https://github.com/lowlighter/metrics#-documentation",
|
|
||||||
name: "Complete documentation",
|
|
||||||
},
|
|
||||||
markdown: {
|
|
||||||
link: "https://github.com/lowlighter/metrics/blob/master/.github/readme/partials/documentation/setup/shared.md",
|
|
||||||
name: "Setup using the shared instance",
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
link: "https://github.com/lowlighter/metrics/blob/master/.github/readme/partials/documentation/setup/action.md",
|
|
||||||
name: "Setup using GitHub Action on a profile repository",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
base: {},
|
|
||||||
list: [],
|
|
||||||
categories: [],
|
|
||||||
enabled: {},
|
|
||||||
descriptions: {
|
|
||||||
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: {...(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: {
|
|
||||||
list: [],
|
|
||||||
selected: "classic",
|
|
||||||
placeholder: {
|
|
||||||
timeout: null,
|
|
||||||
image: "",
|
|
||||||
},
|
|
||||||
descriptions: {
|
|
||||||
classic: "Classic template",
|
|
||||||
terminal: "Terminal template",
|
|
||||||
markdown: "(hidden)",
|
|
||||||
repository: "(hidden)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
generated: {
|
|
||||||
pending: false,
|
|
||||||
content: "",
|
|
||||||
error: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
//Computed data
|
//Computed data
|
||||||
computed: {
|
computed: {
|
||||||
//Unusable plugins
|
|
||||||
unusable() {
|
|
||||||
return this.plugins.list.filter(({name}) => this.plugins.enabled[name]).filter(({enabled}) => !enabled).map(({name}) => name)
|
|
||||||
},
|
|
||||||
//User's avatar
|
|
||||||
avatar() {
|
|
||||||
return this.generated.content ? `https://github.com/${this.user}.png` : null
|
|
||||||
},
|
|
||||||
//User's repository
|
|
||||||
repo() {
|
|
||||||
return `https://github.com/${this.user}/${this.user}`
|
|
||||||
},
|
|
||||||
//Endpoint to use for computed metrics
|
|
||||||
url() {
|
|
||||||
//Plugins enabled
|
|
||||||
const plugins = Object.entries(this.plugins.enabled)
|
|
||||||
.flatMap(([key, value]) => key === "base" ? Object.entries(value).map(([key, value]) => [`base.${key}`, value]) : [[key, value]])
|
|
||||||
.filter(([key, value]) => /^base[.]\w+$/.test(key) ? !value : value)
|
|
||||||
.map(([key, value]) => `${key}=${+value}`)
|
|
||||||
//Plugins options
|
|
||||||
const options = Object.entries(this.plugins.options)
|
|
||||||
.filter(([key, value]) => `${value}`.length)
|
|
||||||
.filter(([key, value]) => this.plugins.enabled[key.split(".")[0]])
|
|
||||||
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
|
||||||
//Base options
|
|
||||||
const base = Object.entries(this.plugins.options).filter(([key, value]) => (key in metadata.base.web) && (value !== metadata.base.web[key]?.defaulted)).map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
|
||||||
//Config
|
|
||||||
const config = Object.entries(this.config).filter(([key, value]) => (value) && (value !== metadata.core.web[key]?.defaulted)).map(([key, value]) => `config.${key}=${encodeURIComponent(value)}`)
|
|
||||||
//Template
|
|
||||||
const template = (this.templates.selected !== this.templates.list[0]) ? [`template=${this.templates.selected}`] : []
|
|
||||||
//Generated url
|
|
||||||
const params = [...template, ...base, ...plugins, ...options, ...config].join("&")
|
|
||||||
return `${window.location.protocol}//${window.location.host}/${this.user}${params.length ? `?${params}` : ""}`
|
|
||||||
},
|
|
||||||
//Embedded generated code
|
|
||||||
embed() {
|
|
||||||
return ``
|
|
||||||
},
|
|
||||||
//Token scopes
|
|
||||||
scopes() {
|
|
||||||
return new Set([
|
|
||||||
...Object.entries(this.plugins.enabled).filter(([key, value]) => (key !== "base") && (value)).flatMap(([key]) => metadata[key].scopes),
|
|
||||||
...(Object.entries(this.plugins.enabled.base).filter(([key, value]) => value).length ? metadata.base.scopes : []),
|
|
||||||
])
|
|
||||||
},
|
|
||||||
//GitHub action auto-generated code
|
|
||||||
action() {
|
|
||||||
return [
|
|
||||||
`# Visit https://github.com/lowlighter/metrics/blob/master/action.yml for full reference`,
|
|
||||||
`name: Metrics`,
|
|
||||||
`on:`,
|
|
||||||
` # Schedule updates (each hour)`,
|
|
||||||
` schedule: [{cron: "0 * * * *"}]`,
|
|
||||||
` # Lines below let you run workflow manually and on each commit`,
|
|
||||||
` workflow_dispatch:`,
|
|
||||||
` push: {branches: ["master", "main"]}`,
|
|
||||||
`jobs:`,
|
|
||||||
` github-metrics:`,
|
|
||||||
` runs-on: ubuntu-latest`,
|
|
||||||
` permissions:`,
|
|
||||||
` contents: write`,
|
|
||||||
` steps:`,
|
|
||||||
` - uses: lowlighter/metrics@latest`,
|
|
||||||
` with:`,
|
|
||||||
...(this.scopes.size
|
|
||||||
? [
|
|
||||||
` # Your GitHub token`,
|
|
||||||
` # The following scopes are required:`,
|
|
||||||
...[...this.scopes].map(scope => ` # - ${scope}${scope === "public_access" ? " (default scope)" : ""}`),
|
|
||||||
` # The following additional scopes may be required:`,
|
|
||||||
` # - read:org (for organization related metrics)`,
|
|
||||||
` # - read:user (for user related data)`,
|
|
||||||
` # - read:packages (for some packages related data)`,
|
|
||||||
` # - repo (optional, if you want to include private repositories)`,
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
` # Current configuration doesn't require a GitHub token`,
|
|
||||||
]),
|
|
||||||
` token: ${this.scopes.size ? `${"$"}{{ secrets.METRICS_TOKEN }}` : "NOT_NEEDED"}`,
|
|
||||||
``,
|
|
||||||
` # Options`,
|
|
||||||
` user: ${this.user}`,
|
|
||||||
` template: ${this.templates.selected}`,
|
|
||||||
` base: ${Object.entries(this.plugins.enabled.base).filter(([key, value]) => value).map(([key]) => key).join(", ") || '""'}`,
|
|
||||||
...[
|
|
||||||
...Object.entries(this.plugins.options).filter(([key, value]) => (key in metadata.base.web) && (value !== metadata.base.web[key]?.defaulted)).map(([key, value]) => ` ${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}`),
|
|
||||||
...Object.entries(this.plugins.enabled).filter(([key, value]) => (key !== "base") && (value)).map(([key]) => ` plugin_${key}: yes`),
|
|
||||||
...Object.entries(this.plugins.options).filter(([key, value]) => (value) && (!(key in metadata.base.web))).filter(([key, value]) => this.plugins.enabled[key.split(".")[0]]).map(([key, value]) => ` plugin_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}`),
|
|
||||||
...Object.entries(this.config).filter(([key, value]) => (value) && (value !== metadata.core.web[key]?.defaulted)).map(([key, value]) => ` config_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}`),
|
|
||||||
].sort(),
|
|
||||||
].join("\n")
|
|
||||||
},
|
|
||||||
//Configurable plugins
|
|
||||||
configure() {
|
|
||||||
//Check enabled plugins
|
|
||||||
const enabled = Object.entries(this.plugins.enabled).filter(([key, value]) => (value) && (key !== "base")).map(([key, value]) => key)
|
|
||||||
const filter = new RegExp(`^(?:${enabled.join("|")})[.]`)
|
|
||||||
//Search related options
|
|
||||||
const entries = Object.entries(this.plugins.options.descriptions).filter(([key, value]) => (filter.test(key)) && (!(key in metadata.base.web)))
|
|
||||||
entries.push(...enabled.map(key => [key, this.plugins.descriptions[key]]))
|
|
||||||
entries.sort((a, b) => a[0].localeCompare(b[0]))
|
|
||||||
//Return object
|
|
||||||
const configure = Object.fromEntries(entries)
|
|
||||||
return Object.keys(configure).length ? configure : null
|
|
||||||
},
|
|
||||||
//Is in preview mode
|
//Is in preview mode
|
||||||
preview() {
|
preview() {
|
||||||
return /-preview$/.test(this.version)
|
return /-preview$/.test(this.version)
|
||||||
},
|
},
|
||||||
//Rate limit reset
|
|
||||||
rlreset() {
|
|
||||||
const reset = new Date(Math.max(this.requests.graphql.reset, this.requests.rest.reset))
|
|
||||||
return `${reset.getHours()}:${reset.getMinutes()}`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
//Methods
|
|
||||||
methods: {
|
|
||||||
//Refresh computed properties
|
|
||||||
async refresh() {
|
|
||||||
const keys = {action: ["scopes", "action"], markdown: ["url", "embed"]}[this.tab]
|
|
||||||
if (keys) {
|
|
||||||
for (const key of keys)
|
|
||||||
this._computedWatchers[key]?.run()
|
|
||||||
this.$forceUpdate()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//Load and render placeholder image
|
|
||||||
async mock({timeout = 600} = {}) {
|
|
||||||
this.refresh()
|
|
||||||
clearTimeout(this.templates.placeholder.timeout)
|
|
||||||
this.templates.placeholder.timeout = setTimeout(async () => {
|
|
||||||
this.templates.placeholder.image = await placeholder(this)
|
|
||||||
this.generated.content = ""
|
|
||||||
this.generated.error = null
|
|
||||||
}, timeout)
|
|
||||||
},
|
|
||||||
//Resize mock image
|
|
||||||
mockresize() {
|
|
||||||
const svg = document.querySelector(".preview .image svg")
|
|
||||||
if ((svg) && (svg.getAttribute("height") == 99999)) {
|
|
||||||
const height = svg.querySelector("#metrics-end")?.getBoundingClientRect()?.y - svg.getBoundingClientRect()?.y
|
|
||||||
if (Number.isFinite(height))
|
|
||||||
svg.setAttribute("height", height)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//Generate metrics and flush cache
|
|
||||||
async generate() {
|
|
||||||
//Avoid requests spamming
|
|
||||||
if (this.generated.pending)
|
|
||||||
return
|
|
||||||
this.generated.pending = true
|
|
||||||
//Compute metrics
|
|
||||||
try {
|
|
||||||
await axios.get(`/.uncache?&token=${(await axios.get(`/.uncache?user=${this.user}`)).data.token}`)
|
|
||||||
this.generated.content = (await axios.get(this.url)).data
|
|
||||||
this.generated.error = null
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
this.generated.error = {code: error.response.status, message: error.response.data}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
this.generated.pending = false
|
|
||||||
try {
|
|
||||||
const {data: requests} = await axios.get("/.requests")
|
|
||||||
this.requests = requests
|
|
||||||
}
|
|
||||||
catch {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
|
|||||||
328
source/app/web/statics/embed/app.js
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
;(async function() {
|
||||||
|
//Init
|
||||||
|
const {data: metadata} = await axios.get("/.plugins.metadata")
|
||||||
|
delete metadata.core.web.output
|
||||||
|
delete metadata.core.web.twemojis
|
||||||
|
//App
|
||||||
|
return new Vue({
|
||||||
|
//Initialization
|
||||||
|
el: "main",
|
||||||
|
async mounted() {
|
||||||
|
//Interpolate config from browser
|
||||||
|
try {
|
||||||
|
this.config.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
|
this.palette = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
||||||
|
}
|
||||||
|
catch (error) {}
|
||||||
|
//Init
|
||||||
|
await Promise.all([
|
||||||
|
//GitHub limit tracker
|
||||||
|
(async () => {
|
||||||
|
const {data: requests} = await axios.get("/.requests")
|
||||||
|
this.requests = requests
|
||||||
|
})(),
|
||||||
|
//Templates
|
||||||
|
(async () => {
|
||||||
|
const {data: templates} = await axios.get("/.templates")
|
||||||
|
templates.sort((a, b) => (a.name.startsWith("@") ^ b.name.startsWith("@")) ? (a.name.startsWith("@") ? 1 : -1) : a.name.localeCompare(b.name))
|
||||||
|
this.templates.list = templates
|
||||||
|
this.templates.selected = templates[0]?.name || "classic"
|
||||||
|
})(),
|
||||||
|
//Plugins
|
||||||
|
(async () => {
|
||||||
|
const {data: plugins} = await axios.get("/.plugins")
|
||||||
|
this.plugins.list = plugins.filter(({name}) => metadata[name]?.supports.includes("user") || metadata[name]?.supports.includes("organization"))
|
||||||
|
const categories = [...new Set(this.plugins.list.map(({category}) => category))]
|
||||||
|
this.plugins.categories = Object.fromEntries(categories.map(category => [category, this.plugins.list.filter(value => category === value.category)]))
|
||||||
|
})(),
|
||||||
|
//Base
|
||||||
|
(async () => {
|
||||||
|
const {data: base} = await axios.get("/.plugins.base")
|
||||||
|
this.plugins.base = base
|
||||||
|
this.plugins.enabled.base = Object.fromEntries(base.map(key => [key, true]))
|
||||||
|
})(),
|
||||||
|
//Version
|
||||||
|
(async () => {
|
||||||
|
const {data: version} = await axios.get("/.version")
|
||||||
|
this.version = `v${version}`
|
||||||
|
})(),
|
||||||
|
//Hosted
|
||||||
|
(async () => {
|
||||||
|
const {data: hosted} = await axios.get("/.hosted")
|
||||||
|
this.hosted = hosted
|
||||||
|
})(),
|
||||||
|
])
|
||||||
|
//Generate placeholder
|
||||||
|
this.mock({timeout: 200})
|
||||||
|
setInterval(() => {
|
||||||
|
const marker = document.querySelector("#metrics-end")
|
||||||
|
if (marker) {
|
||||||
|
this.mockresize()
|
||||||
|
marker.remove()
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
components: {Prism: PrismComponent},
|
||||||
|
//Watchers
|
||||||
|
watch: {
|
||||||
|
tab: {
|
||||||
|
immediate: true,
|
||||||
|
handler(current) {
|
||||||
|
if (current === "action")
|
||||||
|
this.clipboard = new ClipboardJS(".copy-action")
|
||||||
|
else
|
||||||
|
this.clipboard?.destroy()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
palette: {
|
||||||
|
immediate: true,
|
||||||
|
handler(current, previous) {
|
||||||
|
document.querySelector("body").classList.remove(previous)
|
||||||
|
document.querySelector("body").classList.add(current)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
//Data initialization
|
||||||
|
data: {
|
||||||
|
version: "",
|
||||||
|
user: "",
|
||||||
|
tab: "overview",
|
||||||
|
palette: "light",
|
||||||
|
clipboard: null,
|
||||||
|
requests: {rest: {limit: 0, used: 0, remaining: 0, reset: NaN}, graphql: {limit: 0, used: 0, remaining: 0, reset: NaN}},
|
||||||
|
cached: new Map(),
|
||||||
|
config: Object.fromEntries(Object.entries(metadata.core.web).map(([key, {defaulted}]) => [key, defaulted])),
|
||||||
|
metadata: Object.fromEntries(Object.entries(metadata).map(([key, {web}]) => [key, web])),
|
||||||
|
hosted: null,
|
||||||
|
docs: {
|
||||||
|
overview: {
|
||||||
|
link: "https://github.com/lowlighter/metrics#-documentation",
|
||||||
|
name: "Complete documentation",
|
||||||
|
},
|
||||||
|
markdown: {
|
||||||
|
link: "https://github.com/lowlighter/metrics/blob/master/.github/readme/partials/documentation/setup/shared.md",
|
||||||
|
name: "Setup using the shared instance",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
link: "https://github.com/lowlighter/metrics/blob/master/.github/readme/partials/documentation/setup/action.md",
|
||||||
|
name: "Setup using GitHub Action on a profile repository",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
base: {},
|
||||||
|
list: [],
|
||||||
|
categories: [],
|
||||||
|
enabled: {},
|
||||||
|
descriptions: {
|
||||||
|
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: {...(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: {
|
||||||
|
list: [],
|
||||||
|
selected: "classic",
|
||||||
|
placeholder: {
|
||||||
|
timeout: null,
|
||||||
|
image: "",
|
||||||
|
},
|
||||||
|
descriptions: {
|
||||||
|
classic: "Classic template",
|
||||||
|
terminal: "Terminal template",
|
||||||
|
markdown: "(hidden)",
|
||||||
|
repository: "(hidden)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
generated: {
|
||||||
|
pending: false,
|
||||||
|
content: "",
|
||||||
|
error: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
//Computed data
|
||||||
|
computed: {
|
||||||
|
//Unusable plugins
|
||||||
|
unusable() {
|
||||||
|
return this.plugins.list.filter(({name}) => this.plugins.enabled[name]).filter(({enabled}) => !enabled).map(({name}) => name)
|
||||||
|
},
|
||||||
|
//User's avatar
|
||||||
|
avatar() {
|
||||||
|
return this.generated.content ? `https://github.com/${this.user}.png` : null
|
||||||
|
},
|
||||||
|
//User's repository
|
||||||
|
repo() {
|
||||||
|
return `https://github.com/${this.user}/${this.user}`
|
||||||
|
},
|
||||||
|
//Endpoint to use for computed metrics
|
||||||
|
url() {
|
||||||
|
//Plugins enabled
|
||||||
|
const plugins = Object.entries(this.plugins.enabled)
|
||||||
|
.flatMap(([key, value]) => key === "base" ? Object.entries(value).map(([key, value]) => [`base.${key}`, value]) : [[key, value]])
|
||||||
|
.filter(([key, value]) => /^base[.]\w+$/.test(key) ? !value : value)
|
||||||
|
.map(([key, value]) => `${key}=${+value}`)
|
||||||
|
//Plugins options
|
||||||
|
const options = Object.entries(this.plugins.options)
|
||||||
|
.filter(([key, value]) => `${value}`.length)
|
||||||
|
.filter(([key, value]) => this.plugins.enabled[key.split(".")[0]])
|
||||||
|
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
||||||
|
//Base options
|
||||||
|
const base = Object.entries(this.plugins.options).filter(([key, value]) => (key in metadata.base.web) && (value !== metadata.base.web[key]?.defaulted)).map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
||||||
|
//Config
|
||||||
|
const config = Object.entries(this.config).filter(([key, value]) => (value) && (value !== metadata.core.web[key]?.defaulted)).map(([key, value]) => `config.${key}=${encodeURIComponent(value)}`)
|
||||||
|
//Template
|
||||||
|
const template = (this.templates.selected !== this.templates.list[0]) ? [`template=${this.templates.selected}`] : []
|
||||||
|
//Generated url
|
||||||
|
const params = [...template, ...base, ...plugins, ...options, ...config].join("&")
|
||||||
|
return `${window.location.protocol}//${window.location.host}/${this.user}${params.length ? `?${params}` : ""}`
|
||||||
|
},
|
||||||
|
//Embedded generated code
|
||||||
|
embed() {
|
||||||
|
return ``
|
||||||
|
},
|
||||||
|
//Token scopes
|
||||||
|
scopes() {
|
||||||
|
return new Set([
|
||||||
|
...Object.entries(this.plugins.enabled).filter(([key, value]) => (key !== "base") && (value)).flatMap(([key]) => metadata[key].scopes),
|
||||||
|
...(Object.entries(this.plugins.enabled.base).filter(([key, value]) => value).length ? metadata.base.scopes : []),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
//GitHub action auto-generated code
|
||||||
|
action() {
|
||||||
|
return [
|
||||||
|
`# Visit https://github.com/lowlighter/metrics#-documentation for full reference`,
|
||||||
|
`name: Metrics`,
|
||||||
|
`on:`,
|
||||||
|
` # Schedule updates (each hour)`,
|
||||||
|
` schedule: [{cron: "0 * * * *"}]`,
|
||||||
|
` # Lines below let you run workflow manually and on each commit`,
|
||||||
|
` workflow_dispatch:`,
|
||||||
|
` push: {branches: ["master", "main"]}`,
|
||||||
|
`jobs:`,
|
||||||
|
` github-metrics:`,
|
||||||
|
` runs-on: ubuntu-latest`,
|
||||||
|
` permissions:`,
|
||||||
|
` contents: write`,
|
||||||
|
` steps:`,
|
||||||
|
` - uses: lowlighter/metrics@latest`,
|
||||||
|
` with:`,
|
||||||
|
...(this.scopes.size
|
||||||
|
? [
|
||||||
|
` # Your GitHub token`,
|
||||||
|
` # The following scopes are required:`,
|
||||||
|
...[...this.scopes].map(scope => ` # - ${scope}${scope === "public_access" ? " (default scope)" : ""}`),
|
||||||
|
` # The following additional scopes may be required:`,
|
||||||
|
` # - read:org (for organization related metrics)`,
|
||||||
|
` # - read:user (for user related data)`,
|
||||||
|
` # - read:packages (for some packages related data)`,
|
||||||
|
` # - repo (optional, if you want to include private repositories)`,
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
` # Current configuration doesn't require a GitHub token`,
|
||||||
|
]),
|
||||||
|
` token: ${this.scopes.size ? `${"$"}{{ secrets.METRICS_TOKEN }}` : "NOT_NEEDED"}`,
|
||||||
|
``,
|
||||||
|
` # Options`,
|
||||||
|
...(this.user ? [` user: ${this.user}`] : []),
|
||||||
|
` template: ${this.templates.selected}`,
|
||||||
|
` base: ${Object.entries(this.plugins.enabled.base).filter(([key, value]) => value).map(([key]) => key).join(", ") || '""'}`,
|
||||||
|
...[
|
||||||
|
...Object.entries(this.plugins.options).filter(([key, value]) => (key in metadata.base.web) && (value !== metadata.base.web[key]?.defaulted)).map(([key, value]) => ` ${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}`),
|
||||||
|
...Object.entries(this.plugins.enabled).filter(([key, value]) => (key !== "base") && (value)).map(([key]) => ` plugin_${key}: yes`),
|
||||||
|
...Object.entries(this.plugins.options).filter(([key, value]) => (value) && (!(key in metadata.base.web))).filter(([key, value]) => this.plugins.enabled[key.split(".")[0]]).map(([key, value]) => ` plugin_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}`),
|
||||||
|
...Object.entries(this.config).filter(([key, value]) => (value) && (value !== metadata.core.web[key]?.defaulted)).map(([key, value]) => ` config_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}`),
|
||||||
|
].sort(),
|
||||||
|
].join("\n")
|
||||||
|
},
|
||||||
|
//Configurable plugins
|
||||||
|
configure() {
|
||||||
|
//Check enabled plugins
|
||||||
|
const enabled = Object.entries(this.plugins.enabled).filter(([key, value]) => (value) && (key !== "base")).map(([key, value]) => key)
|
||||||
|
const filter = new RegExp(`^(?:${enabled.join("|")})[.]`)
|
||||||
|
//Search related options
|
||||||
|
const entries = Object.entries(this.plugins.options.descriptions).filter(([key, value]) => (filter.test(key)) && (!(key in metadata.base.web)))
|
||||||
|
entries.push(...enabled.map(key => [key, this.plugins.descriptions[key]]))
|
||||||
|
entries.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
|
//Return object
|
||||||
|
const configure = Object.fromEntries(entries)
|
||||||
|
return Object.keys(configure).length ? configure : null
|
||||||
|
},
|
||||||
|
//Is in preview mode
|
||||||
|
preview() {
|
||||||
|
return /-preview$/.test(this.version)
|
||||||
|
},
|
||||||
|
//Rate limit reset
|
||||||
|
rlreset() {
|
||||||
|
const reset = new Date(Math.max(this.requests.graphql.reset, this.requests.rest.reset))
|
||||||
|
return `${reset.getHours()}:${reset.getMinutes()}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
//Methods
|
||||||
|
methods: {
|
||||||
|
//Refresh computed properties
|
||||||
|
async refresh() {
|
||||||
|
const keys = {action: ["scopes", "action"], markdown: ["url", "embed"]}[this.tab]
|
||||||
|
if (keys) {
|
||||||
|
for (const key of keys)
|
||||||
|
this._computedWatchers[key]?.run()
|
||||||
|
this.$forceUpdate()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//Load and render placeholder image
|
||||||
|
async mock({timeout = 600} = {}) {
|
||||||
|
this.refresh()
|
||||||
|
clearTimeout(this.templates.placeholder.timeout)
|
||||||
|
this.templates.placeholder.timeout = setTimeout(async () => {
|
||||||
|
this.templates.placeholder.image = await placeholder(this)
|
||||||
|
this.generated.content = ""
|
||||||
|
this.generated.error = null
|
||||||
|
}, timeout)
|
||||||
|
},
|
||||||
|
//Resize mock image
|
||||||
|
mockresize() {
|
||||||
|
const svg = document.querySelector(".preview .image svg")
|
||||||
|
if ((svg) && (svg.getAttribute("height") == 99999)) {
|
||||||
|
const height = svg.querySelector("#metrics-end")?.getBoundingClientRect()?.y - svg.getBoundingClientRect()?.y
|
||||||
|
if (Number.isFinite(height))
|
||||||
|
svg.setAttribute("height", height)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//Generate metrics and flush cache
|
||||||
|
async generate() {
|
||||||
|
//Avoid requests spamming
|
||||||
|
if (this.generated.pending)
|
||||||
|
return
|
||||||
|
this.generated.pending = true
|
||||||
|
//Compute metrics
|
||||||
|
try {
|
||||||
|
await axios.get(`/.uncache?&token=${(await axios.get(`/.uncache?user=${this.user}`)).data.token}`)
|
||||||
|
this.generated.content = (await axios.get(this.url)).data
|
||||||
|
this.generated.error = null
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
this.generated.error = {code: error.response.status, message: error.response.data}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.generated.pending = false
|
||||||
|
try {
|
||||||
|
const {data: requests} = await axios.get("/.requests")
|
||||||
|
this.requests = requests
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})()
|
||||||
201
source/app/web/statics/embed/index.html
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Metrics</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="An image generator with 20+ metrics about your GitHub account such as activity, community, repositories, coding habits, website performances, music played, starred topics, etc. that you can put on your profile or elsewhere !">
|
||||||
|
<meta name="author" content="lowlighter">
|
||||||
|
<meta property="og:image" content="/.opengraph.png">
|
||||||
|
<link rel="icon" href="/.favicon.png">
|
||||||
|
<link rel="stylesheet" href="/.css/style.vars.css">
|
||||||
|
<link rel="stylesheet" href="/.css/style.css">
|
||||||
|
<link rel="stylesheet" href="/.css/style.prism.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Vue app -->
|
||||||
|
<main :class="[palette]">
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>
|
||||||
|
<a href="https://github.com/lowlighter/metrics">Metrics {{ version }}</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="ui top">
|
||||||
|
<aside></aside>
|
||||||
|
<nav>
|
||||||
|
<div @click="tab = 'overview'" :class="{active:tab === 'overview'}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 1.75a.75.75 0 00-1.5 0v12.5c0 .414.336.75.75.75h14.5a.75.75 0 000-1.5H1.5V1.75zm14.28 2.53a.75.75 0 00-1.06-1.06L10 7.94 7.53 5.47a.75.75 0 00-1.06 0L3.22 8.72a.75.75 0 001.06 1.06L7 7.06l2.47 2.47a.75.75 0 001.06 0l5.25-5.25z"></path></svg>
|
||||||
|
Metrics preview
|
||||||
|
</div>
|
||||||
|
<div @click="tab = 'action'" :class="{active:tab === 'action'}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zM6.379 5.227A.25.25 0 006 5.442v5.117a.25.25 0 00.379.214l4.264-2.559a.25.25 0 000-.428L6.379 5.227z"></path></svg>
|
||||||
|
Action code
|
||||||
|
</div>
|
||||||
|
<div @click="tab = 'markdown'" :class="{active:tab === 'markdown'}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4 1.75C4 .784 4.784 0 5.75 0h5.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0114.25 15h-9a.75.75 0 010-1.5h9a.25.25 0 00.25-.25V6h-2.75A1.75 1.75 0 0110 4.25V1.5H5.75a.25.25 0 00-.25.25v2.5a.75.75 0 01-1.5 0v-2.5zm7.5-.188V4.25c0 .138.112.25.25.25h2.688a.252.252 0 00-.011-.013l-2.914-2.914a.272.272 0 00-.013-.011zM5.72 6.72a.75.75 0 000 1.06l1.47 1.47-1.47 1.47a.75.75 0 101.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0zM3.28 7.78a.75.75 0 00-1.06-1.06l-2 2a.75.75 0 000 1.06l2 2a.75.75 0 001.06-1.06L1.81 9.25l1.47-1.47z"></path></svg>
|
||||||
|
Markdown code
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui">
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
|
||||||
|
<div class="ui-avatar" :style="{backgroundImage:avatar ? `url(${avatar})` : 'none'}"></div>
|
||||||
|
|
||||||
|
<input type="text" v-model="user" placeholder="Your GitHub username" :disabled="generated.pending" @keyup.enter="(!user)||(generated.pending)||(unusable.length > 0)||(!requests.rest.remaining)||(!requests.graphql.remaining) ? null : generate()">
|
||||||
|
<button @click="generate" :disabled="(!user)||(generated.pending)||(unusable.length > 0)||(!requests.rest.remaining)||(!requests.graphql.remaining)">
|
||||||
|
<template v-if="generated.pending">
|
||||||
|
Generating metrics<span class="loading"></span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
Generate your metrics!
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
<small :class="{'error-text':(!requests.rest.remaining)||(!requests.graphql.remaining)}">Remaining GitHub requests:</small>
|
||||||
|
<small>{{ requests.rest.remaining }} REST / {{ requests.graphql.remaining }} GraphQL</small>
|
||||||
|
<small class="warning" v-if="preview">
|
||||||
|
Metrics are rendered by <a href="https://metrics.lecoq.io/">metrics.lecoq.io</a> in preview mode.
|
||||||
|
Any backend editions won't be reflected but client-side rendering can still be tested.
|
||||||
|
</small>
|
||||||
|
<div class="warning" v-if="unusable.length">
|
||||||
|
The following plugins are not available on this web instance: {{ unusable.join(", ") }}
|
||||||
|
</div>
|
||||||
|
<div class="warning" v-if="(!requests.rest.remaining)||(!requests.graphql.remaining)">
|
||||||
|
This web instance has run out of GitHub API requests.
|
||||||
|
Please wait until {{ rlreset }} to generate metrics again.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="configuration">
|
||||||
|
<b>🖼️ Template</b>
|
||||||
|
<label v-for="template in templates.list" :key="template" v-show="templates.descriptions[template.name] !== '(hidden)'" :class="{'not-available':!template.enabled}" :title="!template.enabled ? 'This template is not enabled on this web instance, use GitHub actions instead!' : ''">
|
||||||
|
<input type="radio" v-model="templates.selected" :value="template.name" @change="mock" :disabled="generated.pending">
|
||||||
|
{{ templates.descriptions[template.name] || template.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="configuration" v-if="plugins.base.length">
|
||||||
|
<b>🗃️ Base content</b>
|
||||||
|
<label v-for="part in plugins.base" :key="part">
|
||||||
|
<input type="checkbox" v-model="plugins.enabled.base[part]" @change="mock" :disabled="generated.pending">
|
||||||
|
<span>{{ plugins.descriptions[`base.${part}`] || `base.${part}` }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="configuration plugins" v-if="plugins.list.length">
|
||||||
|
<b>🧩 Additional plugins</b>
|
||||||
|
<template v-for="(category, name) in plugins.categories" :key="category">
|
||||||
|
<details open>
|
||||||
|
<summary>{{ name }}</summary>
|
||||||
|
<label v-for="plugin in category" :class="{'not-available':!plugin.enabled}" :title="!plugin.enabled ? 'This plugin is not enabled on web instance, use it with GitHub actions !' : ''">
|
||||||
|
<input type="checkbox" v-model="plugins.enabled[plugin.name]" @change="mock" :disabled="generated.pending">
|
||||||
|
<div>{{ plugins.descriptions[plugin.name] || plugin.name }}</div>
|
||||||
|
</label>
|
||||||
|
</details>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="configuration" v-if="configure">
|
||||||
|
<b>🔧 Configure plugins</b>
|
||||||
|
<template v-for="(input, key) in configure">
|
||||||
|
<b v-if="typeof input === 'string'">{{ input }}</b>
|
||||||
|
<label v-else class="option">
|
||||||
|
<i>{{ input.text.split("\n")[0] }}</i>
|
||||||
|
<input type="checkbox" v-if="input.type === 'boolean'" v-model="plugins.options[key]" @change="mock">
|
||||||
|
<input type="number" v-else-if="input.type === 'number'" v-model="plugins.options[key]" @change="mock" :min="input.min" :max="input.max">
|
||||||
|
<select v-else-if="input.type === 'select'" v-model="plugins.options[key]" @change="mock">
|
||||||
|
<option v-for="value in input.values" :value="value">{{ value }}</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" v-else v-model="plugins.options[key]" @change="mock" :placeholder="input.placeholder">
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="configuration">
|
||||||
|
<details>
|
||||||
|
<summary><b>⚙️ Additional settings</b></summary>
|
||||||
|
<template v-for="{key, target} in [{key:'base', target:plugins.options}, {key:'core', target:config}]">
|
||||||
|
<template v-for="(input, key) in metadata[key]">
|
||||||
|
<label class="option">
|
||||||
|
<i>{{ input.text.split("\n")[0] }}</i>
|
||||||
|
<input type="checkbox" v-if="input.type === 'boolean'" v-model="target[key]" @change="mock">
|
||||||
|
<input type="number" v-else-if="input.type === 'number'" v-model="target[key]" @change="mock" :min="input.min" :max="input.max">
|
||||||
|
<select v-else-if="input.type === 'select'" v-model="target[key]" @change="mock">
|
||||||
|
<option v-for="value in input.values" :value="value">{{ value }}</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" v-else v-model="target[key]" @change="mock" :placeholder="input.placeholder">
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div class="preview">
|
||||||
|
|
||||||
|
<div class="readmes">
|
||||||
|
<div class="readme">
|
||||||
|
<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M1.326 1.973a1.2 1.2 0 011.49-.832c.387.112.977.307 1.575.602.586.291 1.243.71 1.7 1.296.022.027.042.056.061.084A13.22 13.22 0 018 3c.67 0 1.289.037 1.861.108l.051-.07c.457-.586 1.114-1.004 1.7-1.295a9.654 9.654 0 011.576-.602 1.2 1.2 0 011.49.832c.14.493.356 1.347.479 2.29.079.604.123 1.28.07 1.936.541.977.773 2.11.773 3.301C16 13 14.5 15 8 15s-8-2-8-5.5c0-1.034.238-2.128.795-3.117-.08-.712-.034-1.46.052-2.12.122-.943.34-1.797.479-2.29zM8 13.065c6 0 6.5-2 6-4.27C13.363 5.905 11.25 5 8 5s-5.363.904-6 3.796c-.5 2.27 0 4.27 6 4.27z"></path><path d="M4 8a1 1 0 012 0v1a1 1 0 01-2 0V8zm2.078 2.492c-.083-.264.146-.492.422-.492h3c.276 0 .505.228.422.492C9.67 11.304 8.834 12 8 12c-.834 0-1.669-.696-1.922-1.508zM10 8a1 1 0 112 0v1a1 1 0 11-2 0V8z"></path></svg>
|
||||||
|
<span>{{ user }}</span><span class="slash">/</span>README<span class="md">.md</span>
|
||||||
|
</div>
|
||||||
|
<div class="readme" v-if="tab in docs">
|
||||||
|
<a :href="docs[tab].link">{{ docs[tab].name }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="readme">
|
||||||
|
<a href="https://github.com/lowlighter/metrics/discussions" target="_blank">Send feedback</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="tab == 'overview'">
|
||||||
|
<div class="alert error" v-if="generated.error">
|
||||||
|
An error occurred while generating your metrics :(<br>
|
||||||
|
<small>{{ generated.error.message }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="image" :class="{pending:generated.pending}" v-html="generated.content||templates.placeholder.image"></div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="tab == 'markdown'">
|
||||||
|
Add the markdown below to your <i>README.md</i> <template v-if="user">at <a :href="repo">{{ user }}/{{ user }}</a></template>
|
||||||
|
<div class="code">
|
||||||
|
<Prism language="markdown" :code="embed"></Prism>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="tab == 'action'">
|
||||||
|
<div>
|
||||||
|
<button class="copy-action" data-clipboard-target=".code">Copy Action Code</button>
|
||||||
|
</div>
|
||||||
|
Create a new workflow with the following content <template v-if="user">at <a :href="repo">{{ user }}/{{ user }}</a></template>
|
||||||
|
<div class="code">
|
||||||
|
<Prism language="yaml" :code="action"></Prism>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="https://github.com/lowlighter/metrics">Repository</a>
|
||||||
|
<a href="https://github.com/lowlighter/metrics/blob/master/LICENSE">License</a>
|
||||||
|
<a href="https://github.com/marketplace/actions/metrics-embed">GitHub Action</a>
|
||||||
|
<span v-if="hosted">Hosted with ❤️ by <a :href="hosted.link">{{ hosted.by }}</a></span>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</main>
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="/.js/axios.min.js"></script>
|
||||||
|
<script src="/.js/prism.min.js"></script>
|
||||||
|
<script src="/.js/prism.markdown.min.js"></script>
|
||||||
|
<script src="/.js/prism.yaml.min.js"></script>
|
||||||
|
<script src="/.js/ejs.min.js"></script>
|
||||||
|
<script src="/.js/faker.min.js?v=7.x" type="module"></script>
|
||||||
|
<script src="/.js/vue.min.js"></script>
|
||||||
|
<script src="/.js/vue.prism.min.js"></script>
|
||||||
|
<script src="/.js/clipboard.min.js"></script>
|
||||||
|
<script src="/.js/embed/app.placeholder.js?v=3.26"></script>
|
||||||
|
<script src="/.js/embed/app.js?v=3.26"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
@@ -10,7 +10,6 @@
|
|||||||
<link rel="icon" href="/.favicon.png">
|
<link rel="icon" href="/.favicon.png">
|
||||||
<link rel="stylesheet" href="/.css/style.vars.css">
|
<link rel="stylesheet" href="/.css/style.vars.css">
|
||||||
<link rel="stylesheet" href="/.css/style.css">
|
<link rel="stylesheet" href="/.css/style.css">
|
||||||
<link rel="stylesheet" href="/.css/style.prism.css" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Vue app -->
|
<!-- Vue app -->
|
||||||
@@ -22,165 +21,11 @@
|
|||||||
<a href="https://github.com/lowlighter/metrics">Metrics {{ version }}</a>
|
<a href="https://github.com/lowlighter/metrics">Metrics {{ version }}</a>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="ui top">
|
<main>
|
||||||
<aside></aside>
|
<section class="container center">
|
||||||
<nav>
|
Hi
|
||||||
<div @click="mode = 'metrics', tab = 'overview'" :class="{active:tab === 'overview'}">
|
</section>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 1.75a.75.75 0 00-1.5 0v12.5c0 .414.336.75.75.75h14.5a.75.75 0 000-1.5H1.5V1.75zm14.28 2.53a.75.75 0 00-1.06-1.06L10 7.94 7.53 5.47a.75.75 0 00-1.06 0L3.22 8.72a.75.75 0 001.06 1.06L7 7.06l2.47 2.47a.75.75 0 001.06 0l5.25-5.25z"></path></svg>
|
</main>
|
||||||
Metrics preview
|
|
||||||
</div>
|
|
||||||
<div @click="(user)&&(mode === 'metrics') ? tab = 'action' : null" :class="{active:tab === 'action', disabled:(!user)||(mode !== 'metrics')}">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zM6.379 5.227A.25.25 0 006 5.442v5.117a.25.25 0 00.379.214l4.264-2.559a.25.25 0 000-.428L6.379 5.227z"></path></svg>
|
|
||||||
Action code
|
|
||||||
</div>
|
|
||||||
<div @click="(user)&&(mode === 'metrics') ? tab = 'markdown' : null" :class="{active:tab === 'markdown', disabled:(!user)||(mode !== 'metrics')}">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4 1.75C4 .784 4.784 0 5.75 0h5.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0114.25 15h-9a.75.75 0 010-1.5h9a.25.25 0 00.25-.25V6h-2.75A1.75 1.75 0 0110 4.25V1.5H5.75a.25.25 0 00-.25.25v2.5a.75.75 0 01-1.5 0v-2.5zm7.5-.188V4.25c0 .138.112.25.25.25h2.688a.252.252 0 00-.011-.013l-2.914-2.914a.272.272 0 00-.013-.011zM5.72 6.72a.75.75 0 000 1.06l1.47 1.47-1.47 1.47a.75.75 0 101.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0zM3.28 7.78a.75.75 0 00-1.06-1.06l-2 2a.75.75 0 000 1.06l2 2a.75.75 0 001.06-1.06L1.81 9.25l1.47-1.47z"></path></svg>
|
|
||||||
Markdown code
|
|
||||||
</div>
|
|
||||||
<div @click="mode = 'insights', tab = 'insights'" :class="{active:tab === 'insights'}">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M14.184 1.143a1.75 1.75 0 00-2.502-.57L.912 7.916a1.75 1.75 0 00-.53 2.32l.447.775a1.75 1.75 0 002.275.702l11.745-5.656a1.75 1.75 0 00.757-2.451l-1.422-2.464zm-1.657.669a.25.25 0 01.358.081l1.422 2.464a.25.25 0 01-.108.35l-2.016.97-1.505-2.605 1.85-1.26zM9.436 3.92l1.391 2.41-5.42 2.61-.942-1.63 4.97-3.39zM3.222 8.157l-1.466 1a.25.25 0 00-.075.33l.447.775a.25.25 0 00.325.1l1.598-.769-.83-1.436zm6.253 2.306a.75.75 0 00-.944-.252l-1.809.87a.75.75 0 00-.293.253L4.38 14.326a.75.75 0 101.238.848l1.881-2.75v2.826a.75.75 0 001.5 0v-2.826l1.881 2.75a.75.75 0 001.238-.848l-2.644-3.863z"></path></svg>
|
|
||||||
Metrics Insights
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui" v-if="mode === 'metrics'">
|
|
||||||
|
|
||||||
<aside>
|
|
||||||
|
|
||||||
<div class="ui-avatar" :style="{backgroundImage:avatar ? `url(${avatar})` : 'none'}"></div>
|
|
||||||
|
|
||||||
<input type="text" v-model="user" placeholder="Your GitHub username" :disabled="generated.pending" @keyup.enter="(!user)||(generated.pending)||(unusable.length > 0)||(!requests.rest.remaining)||(!requests.graphql.remaining) ? null : generate()">
|
|
||||||
<button @click="generate" :disabled="(!user)||(generated.pending)||(unusable.length > 0)||(!requests.rest.remaining)||(!requests.graphql.remaining)">
|
|
||||||
<template v-if="generated.pending">
|
|
||||||
Generating metrics<span class="loading"></span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
Generate your metrics!
|
|
||||||
</template>
|
|
||||||
</button>
|
|
||||||
<small :class="{'error-text':(!requests.rest.remaining)||(!requests.graphql.remaining)}">Remaining GitHub requests:</small>
|
|
||||||
<small>{{ requests.rest.remaining }} REST / {{ requests.graphql.remaining }} GraphQL</small>
|
|
||||||
<small class="warning" v-if="preview">
|
|
||||||
Metrics are rendered by <a href="https://metrics.lecoq.io/">metrics.lecoq.io</a> in preview mode.
|
|
||||||
Any backend editions won't be reflected but client-side rendering can still be tested.
|
|
||||||
</small>
|
|
||||||
<div class="warning" v-if="unusable.length">
|
|
||||||
The following plugins are not available on this web instance: {{ unusable.join(", ") }}
|
|
||||||
</div>
|
|
||||||
<div class="warning" v-if="(!requests.rest.remaining)||(!requests.graphql.remaining)">
|
|
||||||
This web instance has run out of GitHub API requests.
|
|
||||||
Please wait until {{ rlreset }} to generate metrics again.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="configuration">
|
|
||||||
<b>🖼️ Template</b>
|
|
||||||
<label v-for="template in templates.list" :key="template" v-show="templates.descriptions[template.name] !== '(hidden)'" :class="{'not-available':!template.enabled}" :title="!template.enabled ? 'This template is not enabled on this web instance, use GitHub actions instead!' : ''">
|
|
||||||
<input type="radio" v-model="templates.selected" :value="template.name" @change="mock" :disabled="generated.pending">
|
|
||||||
{{ templates.descriptions[template.name] || template.name }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="configuration" v-if="plugins.base.length">
|
|
||||||
<b>🗃️ Base content</b>
|
|
||||||
<label v-for="part in plugins.base" :key="part">
|
|
||||||
<input type="checkbox" v-model="plugins.enabled.base[part]" @change="mock" :disabled="generated.pending">
|
|
||||||
<span>{{ plugins.descriptions[`base.${part}`] || `base.${part}` }}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="configuration plugins" v-if="plugins.list.length">
|
|
||||||
<b>🧩 Additional plugins</b>
|
|
||||||
<template v-for="(category, name) in plugins.categories" :key="category">
|
|
||||||
<details open>
|
|
||||||
<summary>{{ name }}</summary>
|
|
||||||
<label v-for="plugin in category" :class="{'not-available':!plugin.enabled}" :title="!plugin.enabled ? 'This plugin is not enabled on web instance, use it with GitHub actions !' : ''">
|
|
||||||
<input type="checkbox" v-model="plugins.enabled[plugin.name]" @change="mock" :disabled="generated.pending">
|
|
||||||
<div>{{ plugins.descriptions[plugin.name] || plugin.name }}</div>
|
|
||||||
</label>
|
|
||||||
</details>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="configuration" v-if="configure">
|
|
||||||
<b>🔧 Configure plugins</b>
|
|
||||||
<template v-for="(input, key) in configure">
|
|
||||||
<b v-if="typeof input === 'string'">{{ input }}</b>
|
|
||||||
<label v-else class="option">
|
|
||||||
<i>{{ input.text.split("\n")[0] }}</i>
|
|
||||||
<input type="checkbox" v-if="input.type === 'boolean'" v-model="plugins.options[key]" @change="mock">
|
|
||||||
<input type="number" v-else-if="input.type === 'number'" v-model="plugins.options[key]" @change="mock" :min="input.min" :max="input.max">
|
|
||||||
<select v-else-if="input.type === 'select'" v-model="plugins.options[key]" @change="mock">
|
|
||||||
<option v-for="value in input.values" :value="value">{{ value }}</option>
|
|
||||||
</select>
|
|
||||||
<input type="text" v-else v-model="plugins.options[key]" @change="mock" :placeholder="input.placeholder">
|
|
||||||
</label>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="configuration">
|
|
||||||
<details>
|
|
||||||
<summary><b>⚙️ Additional settings</b></summary>
|
|
||||||
<template v-for="{key, target} in [{key:'base', target:plugins.options}, {key:'core', target:config}]">
|
|
||||||
<template v-for="(input, key) in metadata[key]">
|
|
||||||
<label class="option">
|
|
||||||
<i>{{ input.text.split("\n")[0] }}</i>
|
|
||||||
<input type="checkbox" v-if="input.type === 'boolean'" v-model="target[key]" @change="mock">
|
|
||||||
<input type="number" v-else-if="input.type === 'number'" v-model="target[key]" @change="mock" :min="input.min" :max="input.max">
|
|
||||||
<select v-else-if="input.type === 'select'" v-model="target[key]" @change="mock">
|
|
||||||
<option v-for="value in input.values" :value="value">{{ value }}</option>
|
|
||||||
</select>
|
|
||||||
<input type="text" v-else v-model="target[key]" @change="mock" :placeholder="input.placeholder">
|
|
||||||
</label>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<div class="preview">
|
|
||||||
|
|
||||||
<div class="readmes">
|
|
||||||
<div class="readme">
|
|
||||||
<svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M1.326 1.973a1.2 1.2 0 011.49-.832c.387.112.977.307 1.575.602.586.291 1.243.71 1.7 1.296.022.027.042.056.061.084A13.22 13.22 0 018 3c.67 0 1.289.037 1.861.108l.051-.07c.457-.586 1.114-1.004 1.7-1.295a9.654 9.654 0 011.576-.602 1.2 1.2 0 011.49.832c.14.493.356 1.347.479 2.29.079.604.123 1.28.07 1.936.541.977.773 2.11.773 3.301C16 13 14.5 15 8 15s-8-2-8-5.5c0-1.034.238-2.128.795-3.117-.08-.712-.034-1.46.052-2.12.122-.943.34-1.797.479-2.29zM8 13.065c6 0 6.5-2 6-4.27C13.363 5.905 11.25 5 8 5s-5.363.904-6 3.796c-.5 2.27 0 4.27 6 4.27z"></path><path d="M4 8a1 1 0 012 0v1a1 1 0 01-2 0V8zm2.078 2.492c-.083-.264.146-.492.422-.492h3c.276 0 .505.228.422.492C9.67 11.304 8.834 12 8 12c-.834 0-1.669-.696-1.922-1.508zM10 8a1 1 0 112 0v1a1 1 0 11-2 0V8z"></path></svg>
|
|
||||||
<span>{{ user }}</span><span class="slash">/</span>README<span class="md">.md</span>
|
|
||||||
</div>
|
|
||||||
<div class="readme" v-if="tab in docs">
|
|
||||||
<a :href="docs[tab].link">{{ docs[tab].name }}</a>
|
|
||||||
</div>
|
|
||||||
<div class="readme">
|
|
||||||
<a href="https://github.com/lowlighter/metrics/discussions" target="_blank">Send feedback</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="tab == 'overview'">
|
|
||||||
<div class="error" v-if="generated.error">
|
|
||||||
An error occurred while generating your metrics :(<br>
|
|
||||||
<small>{{ generated.error.message }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="image" :class="{pending:generated.pending}" v-html="generated.content||templates.placeholder.image"></div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="tab == 'markdown'">
|
|
||||||
Add the markdown below to your <i>README.md</i> at <a :href="repo">{{ user }}/{{ user }}</a>
|
|
||||||
<div class="code">
|
|
||||||
<Prism language="markdown" :code="embed"></Prism>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="tab == 'action'">
|
|
||||||
<div>
|
|
||||||
<button class="copy-action" data-clipboard-target=".code">Copy Action Code</button>
|
|
||||||
</div>
|
|
||||||
Create a new workflow with the following content at <a :href="repo">{{ user }}/{{ user }}</a>
|
|
||||||
<div class="code">
|
|
||||||
<Prism language="yaml" :code="action"></Prism>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<iframe v-else src="/about?embed=1" frameborder="0"></iframe>
|
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<a href="https://github.com/lowlighter/metrics">Repository</a>
|
<a href="https://github.com/lowlighter/metrics">Repository</a>
|
||||||
@@ -193,15 +38,7 @@
|
|||||||
</main>
|
</main>
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="/.js/axios.min.js"></script>
|
<script src="/.js/axios.min.js"></script>
|
||||||
<script src="/.js/prism.min.js"></script>
|
|
||||||
<script src="/.js/prism.markdown.min.js"></script>
|
|
||||||
<script src="/.js/prism.yaml.min.js"></script>
|
|
||||||
<script src="/.js/ejs.min.js"></script>
|
|
||||||
<script src="/.js/faker.min.js?v=7.x" type="module"></script>
|
|
||||||
<script src="/.js/vue.min.js"></script>
|
<script src="/.js/vue.min.js"></script>
|
||||||
<script src="/.js/vue.prism.min.js"></script>
|
|
||||||
<script src="/.js/clipboard.min.js"></script>
|
|
||||||
<script src="/.js/app.placeholder.js?v=3.26"></script>
|
|
||||||
<script src="/.js/app.js?v=3.26"></script>
|
<script src="/.js/app.js?v=3.26"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<link rel="icon" href="/.favicon.png">
|
<link rel="icon" href="/.favicon.png">
|
||||||
<link rel="stylesheet" href="/.css/style.vars.css">
|
<link rel="stylesheet" href="/.css/style.vars.css">
|
||||||
<link rel="stylesheet" href="/.css/style.css">
|
<link rel="stylesheet" href="/.css/style.css">
|
||||||
<link rel="stylesheet" href="/about/.statics/style.css">
|
<link rel="stylesheet" href="/insights/.statics/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Vue app -->
|
<!-- Vue app -->
|
||||||
@@ -585,6 +585,6 @@
|
|||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="/.js/axios.min.js"></script>
|
<script src="/.js/axios.min.js"></script>
|
||||||
<script src="/.js/vue.min.js"></script>
|
<script src="/.js/vue.min.js"></script>
|
||||||
<script src="/about/.statics/script.js?v=3.26"></script>
|
<script src="/insights/.statics/script.js?v=3.26"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
this.localstorage = !!(new URLSearchParams(location.search).get("localstorage"))
|
this.localstorage = !!(new URLSearchParams(location.search).get("localstorage"))
|
||||||
//User
|
//User
|
||||||
const user = location.pathname.split("/").pop()
|
const user = location.pathname.split("/").pop()
|
||||||
if ((user) && (user !== "about")) {
|
if ((user) && (!["about", "insights"].includes(user))) {
|
||||||
this.user = user
|
this.user = user
|
||||||
await this.search()
|
await this.search()
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
this.loaded = ["base", ...Object.keys(this.metrics?.rendered?.plugins ?? {})]
|
this.loaded = ["base", ...Object.keys(this.metrics?.rendered?.plugins ?? {})]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const {processing, ...data} = (await axios.get(`/about/query/${this.user}`)).data
|
const {processing, ...data} = (await axios.get(`/insights/query/${this.user}`)).data
|
||||||
if (processing) {
|
if (processing) {
|
||||||
let completed = 0
|
let completed = 0
|
||||||
this.progress = 1 / (data.plugins.length + 1)
|
this.progress = 1 / (data.plugins.length + 1)
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
return
|
return
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
const {data} = await axios.get(`/about/query/${this.user}/${plugin}`)
|
const {data} = await axios.get(`/insights/query/${this.user}/${plugin}`)
|
||||||
if (!data)
|
if (!data)
|
||||||
throw new Error(`${plugin}: no data`)
|
throw new Error(`${plugin}: no data`)
|
||||||
if (plugin === "base")
|
if (plugin === "base")
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
return {login, name, avatar: this.metrics?.rendered.computed.avatar, type: this.metrics?.rendered.account}
|
return {login, name, avatar: this.metrics?.rendered.computed.avatar, type: this.metrics?.rendered.account}
|
||||||
},
|
},
|
||||||
url() {
|
url() {
|
||||||
return `${window.location.protocol}//${window.location.host}/about/${this.user}`
|
return `${window.location.protocol}//${window.location.host}/insights/${this.user}`
|
||||||
},
|
},
|
||||||
preview() {
|
preview() {
|
||||||
return /-preview$/.test(this.version)
|
return /-preview$/.test(this.version)
|
||||||
@@ -281,7 +281,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Error */
|
/* Error */
|
||||||
.error {
|
.alert.error {
|
||||||
padding: 1.25rem 1rem;
|
padding: 1.25rem 1rem;
|
||||||
background-image: linear-gradient(var(--color-alert-error-bg),var(--color-alert-error-bg));
|
background-image: linear-gradient(var(--color-alert-error-bg),var(--color-alert-error-bg));
|
||||||
color: var(--color-alert-error-text);
|
color: var(--color-alert-error-text);
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import * as compute from "./list/index.mjs"
|
import * as compute from "./list/index.mjs"
|
||||||
|
|
||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, computed, graphql, queries, rest, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, computed, graphql, queries, rest, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.achievements))
|
if ((!enabled) || (!q.achievements) || (!imports.metadata.plugins.achievements.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -37,7 +37,7 @@ export default async function({login, q, imports, data, computed, graphql, queri
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ inputs:
|
|||||||
Enable achievements plugin
|
Enable achievements plugin
|
||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
|
extras:
|
||||||
|
- metrics.run.puppeteer.scrapping
|
||||||
|
|
||||||
plugin_achievements_threshold:
|
plugin_achievements_threshold:
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, rest, q, account, imports}, {enabled = false, markdown = "inline"} = {}) {
|
export default async function({login, data, rest, q, account, imports}, {enabled = false, markdown = "inline", extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.activity))
|
if ((!enabled) || (!q.activity) || (!imports.metadata.plugins.activity.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Context
|
//Context
|
||||||
@@ -174,6 +174,6 @@ export default async function({login, data, rest, q, account, imports}, {enabled
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, queries, imports, q, account}, {enabled = false} = {}) {
|
export default async function({login, data, queries, imports, q, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.anilist))
|
if ((!enabled) || (!q.anilist) || (!imports.metadata.plugins.anilist.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -122,14 +122,7 @@ export default async function({login, data, queries, imports, q, account}, {enab
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
let message = "An error occured"
|
throw imports.format.error(error)
|
||||||
if (error.isAxiosError) {
|
|
||||||
const status = error.response?.status
|
|
||||||
console.debug(error.response.data)
|
|
||||||
message = `API returned ${status}`
|
|
||||||
error = error.response?.data ?? null
|
|
||||||
}
|
|
||||||
throw {error: {message, instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ inputs:
|
|||||||
Enable aniList plugin
|
Enable aniList plugin
|
||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
|
extras:
|
||||||
|
- metrics.run.puppeteer.scrapping
|
||||||
|
|
||||||
plugin_anilist_user:
|
plugin_anilist_user:
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export default async function({login, graphql, rest, data, q, queries, imports,
|
|||||||
//Load inputs
|
//Load inputs
|
||||||
console.debug(`metrics/compute/${login}/base > started`)
|
console.debug(`metrics/compute/${login}/base > started`)
|
||||||
let {indepth, hireable, "repositories.forks": _forks, "repositories.affiliations": _affiliations, "repositories.batch": _batch} = imports.metadata.plugins.base.inputs({data, q, account: "bypass"})
|
let {indepth, hireable, "repositories.forks": _forks, "repositories.affiliations": _affiliations, "repositories.batch": _batch} = imports.metadata.plugins.base.inputs({data, q, account: "bypass"})
|
||||||
const extras = conf.settings.extras?.features ?? conf.settings.extras?.default
|
|
||||||
const repositories = conf.settings.repositories || 100
|
const repositories = conf.settings.repositories || 100
|
||||||
const forks = _forks ? "" : ", isFork: false"
|
const forks = _forks ? "" : ", isFork: false"
|
||||||
const affiliations = _affiliations?.length ? `, ownerAffiliations: [${_affiliations.map(x => x.toLocaleUpperCase()).join(", ")}]${conf.authenticated === login ? `, affiliations: [${_affiliations.map(x => x.toLocaleUpperCase()).join(", ")}]` : ""}` : ""
|
const affiliations = _affiliations?.length ? `, ownerAffiliations: [${_affiliations.map(x => x.toLocaleUpperCase()).join(", ")}]${conf.authenticated === login ? `, affiliations: [${_affiliations.map(x => x.toLocaleUpperCase()).join(", ")}]` : ""}` : ""
|
||||||
@@ -90,7 +89,7 @@ export default async function({login, graphql, rest, data, q, queries, imports,
|
|||||||
}
|
}
|
||||||
//Query contributions collection over account lifetime instead of last year
|
//Query contributions collection over account lifetime instead of last year
|
||||||
if (account === "user") {
|
if (account === "user") {
|
||||||
if ((indepth) && (extras)) {
|
if ((indepth) && (imports.metadata.plugins.base.extras("indepth", {...conf.settings, error:false}))) {
|
||||||
const fields = ["totalRepositoriesWithContributedCommits", "totalCommitContributions", "restrictedContributionsCount", "totalIssueContributions", "totalPullRequestContributions", "totalPullRequestReviewContributions"]
|
const fields = ["totalRepositoriesWithContributedCommits", "totalCommitContributions", "restrictedContributionsCount", "totalIssueContributions", "totalPullRequestContributions", "totalPullRequestReviewContributions"]
|
||||||
const start = new Date(data.user.createdAt)
|
const start = new Date(data.user.createdAt)
|
||||||
const end = new Date()
|
const end = new Date()
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ inputs:
|
|||||||
default: no
|
default: no
|
||||||
extras:
|
extras:
|
||||||
- metrics.api.github.overuse
|
- metrics.api.github.overuse
|
||||||
- plugins.base.indepth
|
|
||||||
|
|
||||||
base_hireable:
|
base_hireable:
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, data, imports, graphql, queries, account}, {enabled = false} = {}) {
|
export default async function({login, q, data, imports, graphql, queries, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.calendar))
|
if ((!enabled) || (!q.calendar) || (!imports.metadata.plugins.calendar.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -52,6 +52,6 @@ export default async function({login, q, data, imports, graphql, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, rest, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, rest, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.code))
|
if ((!enabled) || (!q.code) || (!imports.metadata.plugins.code.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Context
|
//Context
|
||||||
@@ -79,6 +79,6 @@ export default async function({login, q, imports, data, rest, account}, {enabled
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,7 +239,8 @@ export default async function(
|
|||||||
},
|
},
|
||||||
//Settings and tokens
|
//Settings and tokens
|
||||||
{
|
{
|
||||||
enabled = false
|
enabled = false,
|
||||||
|
extras = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({q, data, imports, account}, {enabled = false} = {}) {
|
export default async function({q, data, imports, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.fortune))
|
if ((!enabled) || (!q.fortune) || (!imports.metadata.plugins.fortune.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -40,6 +40,6 @@ export default async function({q, data, imports, account}, {enabled = false} = {
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({q, imports, data, account}, {enabled = false} = {}) {
|
export default async function({q, imports, data, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.nightscout))
|
if ((!enabled) || (!q.nightscout) || (!imports.metadata.plugins.nightscout.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {url, datapoints, lowalert, highalert, urgentlowalert, urgenthighalert} = imports.metadata.plugins.nightscout.inputs({data, account, q})
|
let {url, datapoints, lowalert, highalert, urgentlowalert, urgenthighalert} = imports.metadata.plugins.nightscout.inputs({data, account, q})
|
||||||
|
|
||||||
if (!url || url === "https://example.herokuapp.com")
|
if (!url || url === "https://example.herokuapp.com")
|
||||||
throw {error: {message: "Nightscout site URL isn't set!"}}
|
throw {error: {message: "Nightscout URL is not set"}}
|
||||||
if (url.substring(url.length - 1) !== "/")
|
if (url.substring(url.length - 1) !== "/")
|
||||||
url += "/"
|
url += "/"
|
||||||
if (url.substring(0, 7) === "http://")
|
if (url.substring(0, 7) === "http://")
|
||||||
@@ -48,9 +48,7 @@ export default async function({q, imports, data, account}, {enabled = false} = {
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({q, imports, data, account}, {enabled = false, token = ""} = {}) {
|
export default async function({q, imports, data, account}, {enabled = false, token = "", extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.poopmap))
|
if ((!enabled) || (!q.poopmap) || (!imports.metadata.plugins.poopmap.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if (!token)
|
if (!token)
|
||||||
@@ -32,6 +32,6 @@ export default async function({q, imports, data, account}, {enabled = false, tok
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.screenshot))
|
if ((!enabled) || (!q.screenshot) || (!imports.metadata.plugins.screenshot.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {url, selector, title, background} = imports.metadata.plugins.screenshot.inputs({data, account, q})
|
let {url, selector, title, background} = imports.metadata.plugins.screenshot.inputs({data, account, q})
|
||||||
if (!url)
|
if (!url)
|
||||||
throw {error: {message: "An url is required"}}
|
throw {error: {message: "URL is not set"}}
|
||||||
|
|
||||||
//Start puppeteer and navigate to page
|
//Start puppeteer and navigate to page
|
||||||
console.debug(`metrics/compute/${login}/plugins > screenshot > starting browser`)
|
console.debug(`metrics/compute/${login}/plugins > screenshot > starting browser`)
|
||||||
@@ -37,8 +37,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error, {title:"Screenshot error"})
|
||||||
throw error
|
|
||||||
throw {title: "Screenshot error", error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ inputs:
|
|||||||
Enable screenshot plugin
|
Enable screenshot plugin
|
||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
|
extras:
|
||||||
|
- metrics.run.puppeteer.scrapping
|
||||||
|
|
||||||
plugin_screenshot_title:
|
plugin_screenshot_title:
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, account}, {enabled = false, token} = {}) {
|
export default async function({login, q, imports, data, account}, {enabled = false, extras = false, token} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.stock))
|
if ((!enabled) || (!q.stock) || (!imports.metadata.plugins.stock.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {symbol, interval, duration} = imports.metadata.plugins.stock.inputs({data, account, q})
|
let {symbol, interval, duration} = imports.metadata.plugins.stock.inputs({data, account, q})
|
||||||
if (!token)
|
if (!token)
|
||||||
throw {error: {message: "A token is required"}}
|
throw {error: {message: "API token is not set"}}
|
||||||
if (!symbol)
|
if (!symbol)
|
||||||
throw {error: {message: "A company stock symbol is required"}}
|
throw {error: {message: "Company stock symbol is not set"}}
|
||||||
symbol = symbol.toLocaleUpperCase()
|
symbol = symbol.toLocaleUpperCase()
|
||||||
|
|
||||||
//Query API for company informations
|
//Query API for company informations
|
||||||
@@ -48,13 +48,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
let message = "An error occured"
|
throw imports.format.error(error)
|
||||||
if (error.isAxiosError) {
|
|
||||||
const status = error.response?.status
|
|
||||||
const description = error.response?.data?.message ?? null
|
|
||||||
message = `API returned ${status}${description ? ` (${description})` : ""}`
|
|
||||||
error = error.response?.data ?? null
|
|
||||||
}
|
|
||||||
throw {error: {message, instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export default async function({login, q, imports, data, rest, graphql, queries,
|
|||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.contributors))
|
if ((!enabled) || (!q.contributors) || (!imports.metadata.plugins.contributors.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -70,7 +70,7 @@ export default async function({login, q, imports, data, rest, graphql, queries,
|
|||||||
|
|
||||||
//Contributions categories
|
//Contributions categories
|
||||||
const types = Object.fromEntries([...new Set(Object.keys(categories))].map(type => [type, new Set()]))
|
const types = Object.fromEntries([...new Set(Object.keys(categories))].map(type => [type, new Set()]))
|
||||||
if ((sections.includes("categories")) && (extras)) {
|
if ((sections.includes("categories")) && (imports.metadata.plugins.contributors.extras("categories", {extras}))) {
|
||||||
//Temporary directory
|
//Temporary directory
|
||||||
const repository = `${repo.owner}/${repo.repo}`
|
const repository = `${repo.owner}/${repo.repo}`
|
||||||
const path = imports.paths.join(imports.os.tmpdir(), `${repository.replace(/[^\w]/g, "_")}`)
|
const path = imports.paths.join(imports.os.tmpdir(), `${repository.replace(/[^\w]/g, "_")}`)
|
||||||
@@ -123,6 +123,6 @@ export default async function({login, q, imports, data, rest, graphql, queries,
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ inputs:
|
|||||||
format: comma-separated
|
format: comma-separated
|
||||||
default: ""
|
default: ""
|
||||||
extras:
|
extras:
|
||||||
- metrics.run.setup.community.templates
|
- metrics.setup.community.templates
|
||||||
|
|
||||||
template:
|
template:
|
||||||
description: |
|
description: |
|
||||||
@@ -185,7 +185,7 @@ inputs:
|
|||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
extras:
|
extras:
|
||||||
- metrics.run.user.css
|
- metrics.run.puppeteer.user.css
|
||||||
|
|
||||||
extras_js:
|
extras_js:
|
||||||
description: |
|
description: |
|
||||||
@@ -199,7 +199,7 @@ inputs:
|
|||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
extras:
|
extras:
|
||||||
- metrics.run.user.js
|
- metrics.run.puppeteer.user.js
|
||||||
|
|
||||||
config_timezone:
|
config_timezone:
|
||||||
description: |
|
description: |
|
||||||
@@ -347,6 +347,8 @@ inputs:
|
|||||||
default: ""
|
default: ""
|
||||||
preset: no
|
preset: no
|
||||||
example: "@lunar-red"
|
example: "@lunar-red"
|
||||||
|
extras:
|
||||||
|
- metrics.setup.community.presets
|
||||||
|
|
||||||
retries:
|
retries:
|
||||||
description: |
|
description: |
|
||||||
@@ -497,6 +499,8 @@ inputs:
|
|||||||
default: no
|
default: no
|
||||||
testing: yes
|
testing: yes
|
||||||
preset: no
|
preset: no
|
||||||
|
extras:
|
||||||
|
- metrics.npm.optional.libxml2
|
||||||
|
|
||||||
debug_flags:
|
debug_flags:
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, graphql, queries, data, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, graphql, queries, data, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.discussions))
|
if ((!enabled) || (!q.discussions) || (!imports.metadata.plugins.discussions.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -64,6 +64,6 @@ export default async function({login, q, imports, graphql, queries, data, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.followup))
|
if ((!enabled) || (!q.followup) || (!imports.metadata.plugins.followup.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -56,10 +56,8 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
//Extras features
|
|
||||||
if (extras) {
|
|
||||||
//Indepth mode
|
//Indepth mode
|
||||||
if (indepth) {
|
if ((indepth)&&(imports.metadata.plugins.followup.extras("indepth", {extras}))) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > followup > indepth`)
|
console.debug(`metrics/compute/${login}/plugins > followup > indepth`)
|
||||||
followup.indepth = {repositories: {}}
|
followup.indepth = {repositories: {}}
|
||||||
|
|
||||||
@@ -88,7 +86,6 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//Load user issues and pull requests
|
//Load user issues and pull requests
|
||||||
if ((account === "user") && (sections.includes("user"))) {
|
if ((account === "user") && (sections.includes("user"))) {
|
||||||
@@ -120,6 +117,6 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, graphql, q, imports, queries, account}, {enabled = false} = {}) {
|
export default async function({login, data, graphql, q, imports, queries, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.gists))
|
if ((!enabled) || (!q.gists) || (!imports.metadata.plugins.gists.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -45,8 +45,6 @@ export default async function({login, data, graphql, q, imports, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default async function({login, data, rest, imports, q, account}, {enabled
|
|||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.habits))
|
if ((!enabled) || (!q.habits) || (!imports.metadata.plugins.habits.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -97,7 +97,7 @@ export default async function({login, data, rest, imports, q, account}, {enabled
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Linguist
|
//Linguist
|
||||||
if ((extras) && (charts)) {
|
if ((charts)&&((imports.metadata.plugins.habits.extras("charts", {extras, error:false})))) {
|
||||||
//Check if linguist exists
|
//Check if linguist exists
|
||||||
console.debug(`metrics/compute/${login}/plugins > habits > searching recently used languages using linguist`)
|
console.debug(`metrics/compute/${login}/plugins > habits > searching recently used languages using linguist`)
|
||||||
if (patches.length) {
|
if (patches.length) {
|
||||||
@@ -113,7 +113,7 @@ export default async function({login, data, rest, imports, q, account}, {enabled
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Generating charts with chartist
|
//Generating charts with chartist
|
||||||
if (_charts === "chartist") {
|
if ((_charts === "chartist")&&(imports.metadata.plugins.habits.extras("charts.type", {extras}))) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > habits > generating charts`)
|
console.debug(`metrics/compute/${login}/plugins > habits > generating charts`)
|
||||||
habits.charts = await Promise.all([
|
habits.charts = await Promise.all([
|
||||||
{type: "line", data: {...empty(24), ...Object.fromEntries(Object.entries(habits.commits.hours).filter(([k]) => !Number.isNaN(+k)))}, low: 0, high: habits.commits.hours.max},
|
{type: "line", data: {...empty(24), ...Object.fromEntries(Object.entries(habits.commits.hours).filter(([k]) => !Number.isNaN(+k)))}, low: 0, high: habits.commits.hours.max},
|
||||||
@@ -164,9 +164,7 @@ export default async function({login, data, rest, imports, q, account}, {enabled
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, graphql, queries, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, graphql, queries, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.introduction))
|
if ((!enabled) || (!q.introduction) || (!imports.metadata.plugins.introduction.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -26,6 +26,6 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, graphql, q, imports, queries, account}, {enabled = false} = {}) {
|
export default async function({login, data, graphql, q, imports, queries, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.isocalendar))
|
if ((!enabled) || (!q.isocalendar) || (!imports.metadata.plugins.isocalendar.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -72,9 +72,7 @@ export default async function({login, data, graphql, q, imports, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.languages))
|
if ((!enabled) || (!q.languages) || (!imports.metadata.plugins.languages.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Context
|
//Context
|
||||||
@@ -63,10 +63,8 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Extras features
|
|
||||||
if (extras) {
|
|
||||||
//Recently used languages
|
//Recently used languages
|
||||||
if ((sections.includes("recently-used")) && (context.mode === "user")) {
|
if ((sections.includes("recently-used")) && (context.mode === "user") && (imports.metadata.plugins.languages.extras("indepth", {extras}))) {
|
||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > using recent analyzer`)
|
console.debug(`metrics/compute/${login}/plugins > languages > using recent analyzer`)
|
||||||
languages["stats.recent"] = await recent_analyzer({login, data, imports, rest, account}, {skipped, categories: _recent_categories ?? categories, days: _recent_days, load: _recent_load, timeout})
|
languages["stats.recent"] = await recent_analyzer({login, data, imports, rest, account}, {skipped, categories: _recent_categories ?? categories, days: _recent_days, load: _recent_load, timeout})
|
||||||
@@ -78,7 +76,7 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Indepth mode
|
//Indepth mode
|
||||||
if (indepth) {
|
if ((indepth)&&(imports.metadata.plugins.languages.extras("indepth", {extras}))) {
|
||||||
//Fetch gpg keys (web-flow is GitHub's public key when making changes from web ui)
|
//Fetch gpg keys (web-flow is GitHub's public key when making changes from web ui)
|
||||||
const gpg = []
|
const gpg = []
|
||||||
try {
|
try {
|
||||||
@@ -109,7 +107,6 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
console.debug(`metrics/compute/${login}/plugins > languages > ${error}`)
|
console.debug(`metrics/compute/${login}/plugins > languages > ${error}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//Apply aliases and group languages when needed
|
//Apply aliases and group languages when needed
|
||||||
for (const stats of [languages.stats, languages.lines, languages["stats.recent"].stats, languages["stats.recent"].lines]) {
|
for (const stats of [languages.stats, languages.lines, languages["stats.recent"].stats, languages["stats.recent"].lines]) {
|
||||||
@@ -158,6 +155,6 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ inputs:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
extras:
|
extras:
|
||||||
- metrics.api.github.overuse
|
- metrics.cpu.overuse
|
||||||
- metrics.run.tempdir
|
- metrics.run.tempdir
|
||||||
- metrics.run.git
|
- metrics.run.git
|
||||||
|
|
||||||
@@ -124,10 +124,6 @@ inputs:
|
|||||||
default: 15
|
default: 15
|
||||||
min: 1
|
min: 1
|
||||||
max: 30
|
max: 30
|
||||||
extras:
|
|
||||||
- metrics.api.github.overuse
|
|
||||||
- metrics.run.tempdir
|
|
||||||
- metrics.run.git
|
|
||||||
|
|
||||||
plugin_languages_categories:
|
plugin_languages_categories:
|
||||||
description: |
|
description: |
|
||||||
@@ -140,10 +136,6 @@ inputs:
|
|||||||
- programming
|
- programming
|
||||||
- prose
|
- prose
|
||||||
default: markup, programming
|
default: markup, programming
|
||||||
extras:
|
|
||||||
- metrics.api.github.overuse
|
|
||||||
- metrics.run.tempdir
|
|
||||||
- metrics.run.git
|
|
||||||
|
|
||||||
plugin_languages_recent_categories:
|
plugin_languages_recent_categories:
|
||||||
description: |
|
description: |
|
||||||
@@ -156,10 +148,6 @@ inputs:
|
|||||||
- programming
|
- programming
|
||||||
- prose
|
- prose
|
||||||
default: markup, programming
|
default: markup, programming
|
||||||
extras:
|
|
||||||
- metrics.api.github.overuse
|
|
||||||
- metrics.run.tempdir
|
|
||||||
- metrics.run.git
|
|
||||||
|
|
||||||
plugin_languages_recent_load:
|
plugin_languages_recent_load:
|
||||||
description: |
|
description: |
|
||||||
@@ -168,10 +156,6 @@ inputs:
|
|||||||
default: 300
|
default: 300
|
||||||
min: 100
|
min: 100
|
||||||
max: 1000
|
max: 1000
|
||||||
extras:
|
|
||||||
- metrics.api.github.overuse
|
|
||||||
- metrics.run.tempdir
|
|
||||||
- metrics.run.git
|
|
||||||
|
|
||||||
plugin_languages_recent_days:
|
plugin_languages_recent_days:
|
||||||
description: |
|
description: |
|
||||||
@@ -181,7 +165,3 @@ inputs:
|
|||||||
min: 0
|
min: 0
|
||||||
max: 365
|
max: 365
|
||||||
zero: disable
|
zero: disable
|
||||||
extras:
|
|
||||||
- metrics.api.github.overuse
|
|
||||||
- metrics.run.tempdir
|
|
||||||
- metrics.run.git
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!extras) || (!q.licenses))
|
if ((!enabled) || (!q.licenses) || (!imports.metadata.plugins.licenses.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -113,7 +113,7 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ inputs:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
extras:
|
extras:
|
||||||
|
- metrics.cpu.overuse
|
||||||
- metrics.run.tempdir
|
- metrics.run.tempdir
|
||||||
- metrics.run.git
|
- metrics.run.git
|
||||||
- metrics.run.licensed
|
- metrics.run.licensed
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, imports, rest, q, account}, {enabled = false} = {}) {
|
export default async function({login, data, imports, rest, q, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.lines))
|
if ((!enabled) || (!q.lines) || (!imports.metadata.plugins.lines.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -42,6 +42,6 @@ export default async function({login, data, imports, rest, q, account}, {enabled
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ const modes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Setup
|
//Setup
|
||||||
export default async function({login, imports, data, q, account}, {enabled = false, token = "", sandbox = false} = {}) {
|
export default async function({login, imports, data, q, account}, {enabled = false, token = "", sandbox = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.music))
|
if ((!enabled) || (!q.music) || (!imports.metadata.plugins.music.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Initialization
|
//Initialization
|
||||||
@@ -71,16 +71,16 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Provider
|
//Provider
|
||||||
if (!(provider in providers))
|
if (!(provider in providers))
|
||||||
throw {error: {message: provider ? `Unsupported provider "${provider}"` : "Missing provider"}, ...raw}
|
throw {error: {message: provider ? `Unsupported provider "${provider}"` : "Provider is not set"}, ...raw}
|
||||||
//Mode
|
//Mode
|
||||||
if (!(mode in modes))
|
if (!(mode in modes))
|
||||||
throw {error: {message: `Unsupported mode "${mode}"`}, ...raw}
|
throw {error: {message: `Unsupported mode "${mode}"`}, ...raw}
|
||||||
//Playlist mode
|
//Playlist mode
|
||||||
if (mode === "playlist") {
|
if (mode === "playlist") {
|
||||||
if (!playlist)
|
if (!playlist)
|
||||||
throw {error: {message: "Missing playlist url"}, ...raw}
|
throw {error: {message: "Playlist URL is not set"}, ...raw}
|
||||||
if (!providers[provider].embed.test(playlist))
|
if (!providers[provider].embed.test(playlist))
|
||||||
throw {error: {message: "Unsupported playlist url format"}, ...raw}
|
throw {error: {message: "Unsupported playlist URL format"}, ...raw}
|
||||||
}
|
}
|
||||||
//Limit
|
//Limit
|
||||||
limit = Math.max(1, Math.min(100, Number(limit)))
|
limit = Math.max(1, Math.min(100, Number(limit)))
|
||||||
@@ -177,7 +177,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
//Prepare credentials
|
//Prepare credentials
|
||||||
const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim())
|
const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim())
|
||||||
if ((!client_id) || (!client_secret) || (!refresh_token))
|
if ((!client_id) || (!client_secret) || (!refresh_token))
|
||||||
throw {error: {message: "Spotify token must contain client id/secret and refresh token"}}
|
throw {error: {message: "Token must contain client id, client secret and refresh token"}}
|
||||||
//API call and parse tracklist
|
//API call and parse tracklist
|
||||||
try {
|
try {
|
||||||
//Request access token
|
//Request access token
|
||||||
@@ -309,14 +309,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.isAxiosError) {
|
throw imports.format.error(error)
|
||||||
const status = error.response?.status
|
|
||||||
const description = error.response.data?.error_description ?? null
|
|
||||||
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
|
||||||
error = error.response?.data ?? null
|
|
||||||
throw {error: {message, instance: error}, ...raw}
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -364,9 +357,9 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
//Prepare credentials
|
//Prepare credentials
|
||||||
const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim())
|
const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim())
|
||||||
if ((!client_id) || (!client_secret) || (!refresh_token))
|
if ((!client_id) || (!client_secret) || (!refresh_token))
|
||||||
throw {error: {message: "Spotify token must contain client id/secret and refresh token"}}
|
throw {error: {message: "Token must contain client id, client secret and refresh token"}}
|
||||||
else if (limit > 50)
|
else if (limit > 50)
|
||||||
throw {error: {message: "Spotify top limit cannot be greater than 50"}}
|
throw {error: {message: "Top limit cannot exceed 50 for this provider"}}
|
||||||
|
|
||||||
//API call and parse tracklist
|
//API call and parse tracklist
|
||||||
try {
|
try {
|
||||||
@@ -422,14 +415,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.isAxiosError) {
|
throw imports.format.error(error)
|
||||||
const status = error.response?.status
|
|
||||||
const description = error.response.data?.error_description ?? null
|
|
||||||
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
|
||||||
error = error.response?.data ?? null
|
|
||||||
throw {error: {message, instance: error}, ...raw}
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -473,14 +459,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.isAxiosError) {
|
throw imports.format.error(error)
|
||||||
const status = error.response?.status
|
|
||||||
const description = error.response.data?.message ?? null
|
|
||||||
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
|
||||||
error = error.response?.data ?? null
|
|
||||||
throw {error: {message, instance: error}, ...raw}
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -513,13 +492,11 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Unhandled error
|
//Unhandled error
|
||||||
throw {error: {message: "An error occured (could not retrieve tracks)"}}
|
throw {error: {message: "Failed to retrieve tracks"}}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ inputs:
|
|||||||
Enable music plugin
|
Enable music plugin
|
||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
|
extras:
|
||||||
|
- metrics.run.puppeteer.scrapping
|
||||||
|
|
||||||
plugin_music_provider:
|
plugin_music_provider:
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.notable))
|
if ((!enabled) || (!q.notable) || (!imports.metadata.plugins.notable.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -32,10 +32,8 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
contributions = await Promise.all(contributions.map(async ({handle, stars, issues, pulls, avatarUrl, organization}) => ({name: handle.split("/").shift(), handle, stars, issues, pulls, avatar: await imports.imgb64(avatarUrl), organization})))
|
contributions = await Promise.all(contributions.map(async ({handle, stars, issues, pulls, avatarUrl, organization}) => ({name: handle.split("/").shift(), handle, stars, issues, pulls, avatar: await imports.imgb64(avatarUrl), organization})))
|
||||||
console.debug(`metrics/compute/${login}/plugins > notable > found ${contributions.length} notable contributions`)
|
console.debug(`metrics/compute/${login}/plugins > notable > found ${contributions.length} notable contributions`)
|
||||||
|
|
||||||
//Extras features
|
|
||||||
if (extras) {
|
|
||||||
//Indepth
|
//Indepth
|
||||||
if (indepth) {
|
if ((indepth)&&(imports.metadata.plugins.notable.extras("indepth", {extras}))) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > notable > indepth`)
|
console.debug(`metrics/compute/${login}/plugins > notable > indepth`)
|
||||||
|
|
||||||
//Fetch issues
|
//Fetch issues
|
||||||
@@ -103,7 +101,6 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//Aggregate contributions
|
//Aggregate contributions
|
||||||
console.debug(`metrics/compute/${login}/plugins > notable > aggregating results`)
|
console.debug(`metrics/compute/${login}/plugins > notable > aggregating results`)
|
||||||
@@ -142,6 +139,6 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, imports, data, q, account}, {enabled = false, token = null} = {}) {
|
export default async function({login, imports, data, q, account}, {enabled = false, token = null, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.pagespeed) || ((!data.user.websiteUrl) && (!q["pagespeed.url"])))
|
if ((!enabled) || (!q.pagespeed) || (!imports.metadata.plugins.pagespeed.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {detailed, screenshot, url, pwa} = imports.metadata.plugins.pagespeed.inputs({data, account, q})
|
let {detailed, screenshot, url, pwa} = imports.metadata.plugins.pagespeed.inputs({data, account, q})
|
||||||
//Format url if needed
|
//Format url if needed
|
||||||
|
if (!url)
|
||||||
|
throw {error: {message: "Website URL is not set"}}
|
||||||
if (!/^https?:[/][/]/.test(url))
|
if (!/^https?:[/][/]/.test(url))
|
||||||
url = `https://${url}`
|
url = `https://${url}`
|
||||||
const {protocol, host} = imports.url.parse(url)
|
const {protocol, host} = imports.url.parse(url)
|
||||||
@@ -45,15 +47,13 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
let message = "An error occured"
|
throw imports.format.error(error, {descriptions:{"429":'(consider using "plugin_pagespeed_token")', custom(error) {
|
||||||
if (error.isAxiosError) {
|
const description = error.response?.data?.error?.message?.match(/Lighthouse returned error: (?<description>[A-Z_]+)/)?.groups?.description ?? null
|
||||||
|
if (description) {
|
||||||
const status = error.response?.status
|
const status = error.response?.status
|
||||||
let description = error.response?.data?.error?.message?.match(/Lighthouse returned error: (?<description>[A-Z_]+)/)?.groups?.description ?? null
|
return `API error: ${status} (${description})`
|
||||||
if ((status === 429) && (!description))
|
|
||||||
description = 'consider using "plugin_pagespeed_token"'
|
|
||||||
message = `API returned ${status}${description ? ` (${description})` : ""}`
|
|
||||||
error = error.response?.data ?? null
|
|
||||||
}
|
}
|
||||||
throw {error: {message, instance: error}}
|
return null
|
||||||
|
}}})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, graphql, rest, q, queries, imports, account}, {enabled = false} = {}) {
|
export default async function({login, data, graphql, rest, q, queries, imports, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.people))
|
if ((!enabled) || (!q.people) || (!imports.metadata.plugins.people.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Context
|
//Context
|
||||||
@@ -102,6 +102,6 @@ export default async function({login, data, graphql, rest, q, queries, imports,
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, imports, q, queries, account}, {enabled = false} = {}) {
|
export default async function({login, data, imports, q, queries, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.posts))
|
if ((!enabled) || (!q.posts) || (!imports.metadata.plugins.posts.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -51,12 +51,10 @@ export default async function({login, data, imports, q, queries, account}, {enab
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Unhandled error
|
//Unhandled error
|
||||||
throw {error: {message: "An error occured (could not retrieve posts)"}}
|
throw {error: {message: "Failed to retrieve posts"}}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, imports, graphql, q, queries, account}, {enabled = false} = {}) {
|
export default async function({login, data, imports, graphql, q, queries, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.projects))
|
if ((!enabled) || (!q.projects) || (!imports.metadata.plugins.projects.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -66,9 +66,10 @@ export default async function({login, data, imports, graphql, q, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
let message = "An error occured"
|
throw imports.format.error(error, {descriptions:{custom(error) {
|
||||||
if (error.errors?.map(({type}) => type)?.includes("INSUFFICIENT_SCOPES"))
|
if (error.errors?.map(({type}) => type)?.includes("INSUFFICIENT_SCOPES"))
|
||||||
message = "Insufficient token rights"
|
return "Insufficient token scopes"
|
||||||
throw {error: {message, instance: error}}
|
return null
|
||||||
|
}}})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, graphql, queries, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, graphql, queries, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.reactions))
|
if ((!enabled) || (!q.reactions) || (!imports.metadata.plugins.reactions.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -60,6 +60,6 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, graphql, queries, data, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, graphql, queries, data, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.repositories))
|
if ((!enabled) || (!q.repositories) || (!imports.metadata.plugins.repositories.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -30,7 +30,7 @@ export default async function({login, q, imports, graphql, queries, data, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.rss))
|
if ((!enabled) || (!q.rss) || (!imports.metadata.plugins.rss.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {source, limit} = imports.metadata.plugins.rss.inputs({data, account, q})
|
let {source, limit} = imports.metadata.plugins.rss.inputs({data, account, q})
|
||||||
if (!source)
|
if (!source)
|
||||||
throw {error: {message: "A RSS feed is required"}}
|
throw {error: {message: "RSS feed URL is not set"}}
|
||||||
|
|
||||||
//Load rss feed
|
//Load rss feed
|
||||||
const {title, description, link, items} = await (new imports.rss()).parseURL(source) //eslint-disable-line new-cap
|
const {title, description, link, items} = await (new imports.rss()).parseURL(source) //eslint-disable-line new-cap
|
||||||
@@ -26,8 +26,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.skyline))
|
if ((!enabled) || (!q.skyline) || (!imports.metadata.plugins.skyline.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -42,6 +42,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ inputs:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
extras:
|
extras:
|
||||||
|
- metrics.cpu.overuse
|
||||||
- metrics.npm.optional.gifencoder
|
- metrics.npm.optional.gifencoder
|
||||||
|
- metrics.run.puppeteer.scrapping
|
||||||
|
|
||||||
plugin_skyline_year:
|
plugin_skyline_year:
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, graphql, queries, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, graphql, queries, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.sponsors))
|
if ((!enabled) || (!q.sponsors) || (!imports.metadata.plugins.sponsors.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -85,6 +85,6 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.stackoverflow))
|
if ((!enabled) || (!q.stackoverflow) || (!imports.metadata.plugins.stackoverflow.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {sections, user, limit, lines, "lines.snippet": codelines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q})
|
let {sections, user, limit, lines, "lines.snippet": codelines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q})
|
||||||
if (!user)
|
if (!user)
|
||||||
throw {error: {message: "You must provide a stackoverflow user id"}}
|
throw {error: {message: "Stack Overflow user id is not set"}}
|
||||||
|
|
||||||
//Initialization
|
//Initialization
|
||||||
//See https://api.stackexchange.com/docs
|
//See https://api.stackexchange.com/docs
|
||||||
@@ -64,9 +64,7 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, graphql, data, imports, q, queries, account}, {enabled = false} = {}) {
|
export default async function({login, graphql, data, imports, q, queries, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.stargazers))
|
if ((!enabled) || (!q.stargazers) || (!imports.metadata.plugins.stargazers.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -59,7 +59,7 @@ export default async function({login, graphql, data, imports, q, queries, accoun
|
|||||||
|
|
||||||
//Generating charts
|
//Generating charts
|
||||||
let charts = null
|
let charts = null
|
||||||
if (_charts === "chartist") {
|
if ((_charts === "chartist")&&(imports.metadata.plugins.stargazers.extras("charts.type", {extras}))) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > stargazers > generating charts`)
|
console.debug(`metrics/compute/${login}/plugins > stargazers > generating charts`)
|
||||||
charts = await Promise.all([{data: total, low: total.min, high: total.max}, {data: increments, ref: 0, low: increments.min, high: increments.max, sign: true}].map(({data: {dates: set}, high, low, ref, sign = false}) =>
|
charts = await Promise.all([{data: total, low: total.min, high: total.max}, {data: increments, ref: 0, low: increments.min, high: increments.max, sign: true}].map(({data: {dates: set}, high, low, ref, sign = false}) =>
|
||||||
imports.chartist("line", {
|
imports.chartist("line", {
|
||||||
@@ -100,6 +100,6 @@ export default async function({login, graphql, data, imports, q, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.starlists))
|
if ((!enabled) || (!q.starlists) || (!imports.metadata.plugins.starlists.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -101,6 +101,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
throw imports.format.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ inputs:
|
|||||||
Enable starlists plugin
|
Enable starlists plugin
|
||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
|
extras:
|
||||||
|
- metrics.run.puppeteer.scrapping
|
||||||
|
|
||||||
plugin_starlists_limit:
|
plugin_starlists_limit:
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, graphql, q, queries, imports, account}, {enabled = false} = {}) {
|
export default async function({login, data, graphql, q, queries, imports, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.stars))
|
if ((!enabled) || (!q.stars) || (!imports.metadata.plugins.stars.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -30,8 +30,6 @@ export default async function({login, data, graphql, q, queries, imports, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, account}, {enabled = false} = {}) {
|
export default async function({login, q, imports, data, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.support))
|
if ((!enabled) || (!q.support) || (!imports.metadata.plugins.support.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -24,7 +24,7 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
await frame.waitForSelector(".user-profile-names", {timeout: 5000})
|
await frame.waitForSelector(".user-profile-names", {timeout: 5000})
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
throw {error: {message: "Could not find matching account on github.community"}}
|
throw {error: {message: "Account does not exists on github.community"}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +81,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ inputs:
|
|||||||
Enable support plugin
|
Enable support plugin
|
||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
|
extras:
|
||||||
|
- metrics.run.puppeteer.scrapping
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, imports, q, account}, {enabled = false} = {}) {
|
export default async function({login, data, imports, q, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.topics))
|
if ((!enabled) || (!q.topics) || (!imports.metadata.plugins.topics.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -88,8 +88,6 @@ export default async function({login, data, imports, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
throw imports.format.error(error)
|
||||||
throw error
|
|
||||||
throw {error: {message: "An error occured", instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ inputs:
|
|||||||
Enable topics plugin
|
Enable topics plugin
|
||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
|
extras:
|
||||||
|
- metrics.run.puppeteer.scrapping
|
||||||
|
|
||||||
plugin_topics_mode:
|
plugin_topics_mode:
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, imports, data, rest, q, account}, {enabled = false} = {}) {
|
export default async function({login, imports, data, rest, q, account}, {enabled = false, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.traffic))
|
if ((!enabled) || (!q.traffic) || (!imports.metadata.plugins.traffic.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -27,9 +27,6 @@ export default async function({login, imports, data, rest, q, account}, {enabled
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
let message = "An error occured"
|
throw imports.format.error(error, {descriptions:{"403":"Insufficient token scopes"}})
|
||||||
if (error.status === 403)
|
|
||||||
message = "Insufficient token rights"
|
|
||||||
throw {error: {message, instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, imports, data, q, account}, {enabled = false, token = ""} = {}) {
|
export default async function({login, imports, data, q, account}, {enabled = false, token = "", extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if ((!enabled) || (!q.tweets))
|
if ((!enabled) || (!q.tweets) || (!imports.metadata.plugins.tweets.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -93,13 +93,6 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
let message = "An error occured"
|
throw imports.format.error(error)
|
||||||
if (error.isAxiosError) {
|
|
||||||
const status = error.response?.status
|
|
||||||
const description = error.response?.data?.errors?.[0]?.message ?? null
|
|
||||||
message = `API returned ${status}${description ? ` (${description})` : ""}`
|
|
||||||
error = error.response?.data ?? null
|
|
||||||
}
|
|
||||||
throw {error: {message, instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q, imports, data, account}, {enabled = false, token} = {}) {
|
export default async function({login, q, imports, data, account}, {enabled = false, token, extras = false} = {}) {
|
||||||
//Plugin execution
|
//Plugin execution
|
||||||
try {
|
try {
|
||||||
//Check if plugin is enabled and requirements are met
|
//Check if plugin is enabled and requirements are met
|
||||||
if (!enabled || !q.wakatime)
|
if ((!enabled) || (!q.wakatime) || (!imports.metadata.plugins.wakatime.extras("enabled", {extras})))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -46,13 +46,7 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
let message = "An error occured"
|
throw imports.format.error(error)
|
||||||
if (error.isAxiosError) {
|
|
||||||
const status = error.response?.status
|
|
||||||
message = `API returned ${status}`
|
|
||||||
error = error.response?.data ?? null
|
|
||||||
}
|
|
||||||
throw {error: {message, instance: error}}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
{"source": "/:login([-\\w]+)/:repository([-\\w]+)", "destination": "https://metrics.lecoq.io/:login/:repository"},
|
{"source": "/:login([-\\w]+)/:repository([-\\w]+)", "destination": "https://metrics.lecoq.io/:login/:repository"},
|
||||||
{"source": "/about/query/:login", "destination": "https://metrics.lecoq.io/about/query/:login"},
|
{"source": "/about/query/:login", "destination": "https://metrics.lecoq.io/about/query/:login"},
|
||||||
{"source": "/about/query/:login/:plugin", "destination": "https://metrics.lecoq.io/about/query/:login/:plugin"},
|
{"source": "/about/query/:login/:plugin", "destination": "https://metrics.lecoq.io/about/query/:login/:plugin"},
|
||||||
|
{"source": "/insights/query/:login", "destination": "https://metrics.lecoq.io/insights/query/:login"},
|
||||||
|
{"source": "/insights/query/:login/:plugin", "destination": "https://metrics.lecoq.io/insights/query/:login/:plugin"},
|
||||||
{"source": "/.uncache", "destination": "https://metrics.lecoq.io/.uncache"},
|
{"source": "/.uncache", "destination": "https://metrics.lecoq.io/.uncache"},
|
||||||
{"source": "/.requests", "destination": "https://metrics.lecoq.io/.requests"}
|
{"source": "/.requests", "destination": "https://metrics.lecoq.io/.requests"}
|
||||||
],
|
],
|
||||||
|
|||||||