From 3945af02d1be39ee499e40ebe728df7be895f281 Mon Sep 17 00:00:00 2001 From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com> Date: Fri, 28 Jan 2022 02:22:42 +0100 Subject: [PATCH] feat(app/web): update ratelimit and placeholders (#826) --- source/app/web/instance.mjs | 32 +- source/app/web/statics/about/index.html | 11 +- source/app/web/statics/about/script.js | 11 +- source/app/web/statics/app.js | 25 +- source/app/web/statics/app.placeholder.js | 47 +- source/app/web/statics/index.html | 25 +- .../placeholders/isocalendar.full-year.svg | 1964 +++++++++++++++++ .../placeholders/isocalendar.half-year.svg | 1002 +++++++++ .../web/statics/placeholders/screenshot.png | Bin 0 -> 101296 bytes .../app/web/statics/placeholders/skyline.png | Bin 0 -> 88912 bytes source/app/web/statics/placeholders/stock.svg | 34 + source/app/web/statics/style.css | 1 + 12 files changed, 3124 insertions(+), 28 deletions(-) create mode 100644 source/app/web/statics/placeholders/isocalendar.full-year.svg create mode 100644 source/app/web/statics/placeholders/isocalendar.half-year.svg create mode 100644 source/app/web/statics/placeholders/screenshot.png create mode 100644 source/app/web/statics/placeholders/skyline.png create mode 100644 source/app/web/statics/placeholders/stock.svg diff --git a/source/app/web/instance.mjs b/source/app/web/instance.mjs index 872b1523..0af6cf1b 100644 --- a/source/app/web/instance.mjs +++ b/source/app/web/instance.mjs @@ -12,13 +12,13 @@ import presets from "../metrics/presets.mjs" import setup from "../metrics/setup.mjs" /**App */ -export default async function({sandbox} = {}) { +export default async function({sandbox = false} = {}) { //Load configuration settings const {conf, Plugins, Templates} = await setup({sandbox}) //Sandbox mode if (sandbox) { console.debug("metrics/app > sandbox mode is specified, enabling advanced features") - Object.assign(conf.settings, {optimize: true, cached:0, "plugins.default":true, extras:{default:true}}) + Object.assign(conf.settings, {sandbox:true, optimize: true, cached:0, "plugins.default":true, extras:{default:true}}) } const {token, maxusers = 0, restricted = [], debug = false, cached = 30 * 60 * 1000, port = 3000, ratelimiter = null, plugins = null} = conf.settings const mock = sandbox || conf.settings.mocked @@ -93,17 +93,28 @@ export default async function({sandbox} = {}) { const enabled = Object.entries(metadata).filter(([_name, {category}]) => category !== "core").map(([name]) => ({name, category:metadata[name]?.category ?? "community", enabled:plugins[name]?.enabled ?? false})) const templates = Object.entries(Templates).map(([name]) => ({name, enabled:(conf.settings.templates.enabled.length ? conf.settings.templates.enabled.includes(name) : true) ?? false})) const actions = {flush:new Map()} - let requests = {limit:0, used:0, remaining:0, reset:NaN} + const requests = {rest:{limit:0, used:0, remaining:0, reset:NaN}, graphql:{limit:0, used:0, remaining:0, reset:NaN}} + let _requests_refresh = false if (!conf.settings.notoken) { - requests = (await rest.rateLimit.get()).data.rate - setInterval(async () => { + const refresh = async () => { try { - requests = (await rest.rateLimit.get()).data.rate + const {limit} = await graphql("{ limit:rateLimit {limit remaining reset:resetAt used} }") + Object.assign(requests, { + rest:(await rest.rateLimit.get()).data.rate, + graphql:{...limit, reset:new Date(limit.reset).getTime()} + }) } catch { console.debug("metrics/app > failed to update remaining requests") } - }, 5 * 60 * 1000) + } + await refresh() + setInterval(refresh, 15 * 60 * 1000) + setInterval(() => { + if (_requests_refresh) + refresh() + _requests_refresh = false + }, 15 * 1000) } //Web app.get("/", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/index.html`)) @@ -119,6 +130,8 @@ export default async function({sandbox} = {}) { 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) app.use(`/.templates/${template}/partials`, express.static(`${conf.paths.templates}/${template}/partials`)) + //Placeholders + app.use("/.placeholders", express.static(`${conf.paths.statics}/placeholders`)) //Styles 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`)) @@ -205,6 +218,9 @@ export default async function({sandbox} = {}) { console.error(error) return res.status(500).send("Internal Server Error: failed to process metrics correctly") } + finally { + _requests_refresh = true + } }) //Metrics @@ -309,8 +325,8 @@ export default async function({sandbox} = {}) { } finally { //After rendering - solve?.() + _requests_refresh = true } }) diff --git a/source/app/web/statics/about/index.html b/source/app/web/statics/about/index.html index 61783dbc..035a3a9e 100644 --- a/source/app/web/statics/about/index.html +++ b/source/app/web/statics/about/index.html @@ -1,3 +1,4 @@ + @@ -28,7 +29,7 @@ Search a GitHub user - {{ requests.remaining }} GitHub requests remaining + Remaining GitHub requests: {{ requests.rest.remaining }} REST / {{ requests.graphql.remaining }} GraphQL Send feedback on GitHub discussions!
@@ -42,6 +43,10 @@
+
+ This web instance has run out of GitHub API requests. + Please wait until {{ rlreset }} to generate metrics again. +
Display rankings, contributions, highlights, commits calendar, used languages and recent activity from any user account! @@ -237,7 +242,7 @@
{{ habits[h] }} -
+
{{ `${h}`.padStart(2, 0) }}
@@ -369,6 +374,6 @@ - + diff --git a/source/app/web/statics/about/script.js b/source/app/web/statics/about/script.js index 8895b238..7ab67d38 100644 --- a/source/app/web/statics/about/script.js +++ b/source/app/web/statics/about/script.js @@ -93,6 +93,10 @@ } finally { this.pending = false + try { + const { data: requests } = await axios.get("/.requests") + this.requests = requests + } catch {} } }, }, @@ -111,6 +115,7 @@ return this.metrics?.rendered.plugins.followup ?? null }, habits() { + console.log(this.metrics?.rendered.plugins.habits.commits.hours) return this.metrics?.rendered.plugins.habits.commits.hours ?? null }, isocalendar() { @@ -142,6 +147,10 @@ preview() { return /-preview$/.test(this.version) }, + rlreset() { + const reset = new Date(Math.max(this.requests.graphql.reset, this.requests.rest.reset)) + return `${reset.getHours()}:${reset.getMinutes()}` + } }, //Data initialization data: { @@ -151,7 +160,7 @@ embed: false, localstorage: false, searchable: false, - requests: { limit: 0, used: 0, remaining: 0, reset: 0 }, + requests: {rest:{limit:0, used:0, remaining:0, reset:NaN}, graphql:{limit:0, used:0, remaining:0, reset:NaN}}, palette: "light", metrics: null, pending: false, diff --git a/source/app/web/statics/app.js b/source/app/web/statics/app.js index d685ba94..e8737217 100644 --- a/source/app/web/statics/app.js +++ b/source/app/web/statics/app.js @@ -90,11 +90,25 @@ tab: "overview", palette: "light", clipboard: null, - requests: { limit: 0, used: 0, remaining: 0, reset: 0 }, + 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: [], @@ -251,6 +265,11 @@ 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: { @@ -299,6 +318,10 @@ } finally { this.generated.pending = false + try { + const { data: requests } = await axios.get("/.requests") + this.requests = requests + } catch {} } }, }, diff --git a/source/app/web/statics/app.placeholder.js b/source/app/web/statics/app.placeholder.js index afd92284..abb05efc 100644 --- a/source/app/web/statics/app.placeholder.js +++ b/source/app/web/statics/app.placeholder.js @@ -18,6 +18,12 @@ values.push(probability) return values.sort((a, b) => b - a) } + //Static complex placeholder + async function staticPlaceholder(condition, name) { + if (!condition) + return "" + return await fetch(`/.placeholders/${name}`).then(response => response.text()).catch(() => "(could not render placeholder)") + } //Placeholder function globalThis.placeholder = async function(set) { //Load templates informations @@ -241,8 +247,21 @@ ? ({ notable: { contributions: new Array(2 + faker.datatype.number(2)).fill(null).map(_ => ({ - name: `${options["notable.repositories"] ? `${faker.lorem.slug()}/` : ""}${faker.lorem.slug()}`, + get name() { return options["notable.repositories"] ? this.handle : this.handle.split("/")[0] }, + handle: `${faker.lorem.slug()}/${faker.lorem.slug()}`, avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", + organization: faker.datatype.boolean(), + stars: faker.datatype.number(1000), + aggregated: faker.datatype.number(100), + history: faker.datatype.number(1000), + ...(options["notable.indepth"] ? { + user:{ + commits: faker.datatype.number(100), + percentage: faker.datatype.float({ max: 1 }), + maintainer: false, + stars: faker.datatype.number(100), + } + } : null) })), }, }) @@ -322,7 +341,7 @@ }, }, comments: options["reactions.limit"], - details: options["reactions.details"], + details: options["reactions.details"].split(",").map(x => x.trim()), days: options["reactions.days"], }, }) @@ -451,7 +470,7 @@ ...(set.plugins.enabled.stock ? ({ stock: { - chart: "(stock chart is not displayed in placeholder)", + chart: await staticPlaceholder(set.plugins.enabled.stock, "stock.svg"), currency: "USD", price: faker.datatype.number(10000) / 100, previous: faker.datatype.number(10000) / 100, @@ -553,10 +572,12 @@ music: { provider: "(music provider)", mode: "Suggested tracks", + played_at: options["music.played.at"], tracks: new Array(Number(options["music.limit"])).fill(null).map(_ => ({ name: faker.random.words(5), artist: faker.random.words(), artwork: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", + played_at: options["music.played.at"] ? faker.date.recent() : null, })), }, }) @@ -578,6 +599,16 @@ }, }) : null), + //Fortune + ...(set.plugins.enabled.fortune + ? ({ + fortune: faker.random.arrayElement([ + {chance:.06, color:"#43FD3B", text:"Good news will come to you by mail"}, + {chance:.06, color:"#00CBB0", text:"キタ━━━━━━(゚∀゚)━━━━━━ !!!!"}, + {chance: 0.03, color: "#FD4D32", text: "Excellent Luck"} + ]), + }) + : null), //Pagespeed ...(set.plugins.enabled.pagespeed ? ({ @@ -666,6 +697,7 @@ started: faker.datatype.number(1000), comments: faker.datatype.number(1000), answers: faker.datatype.number(1000), + display: { categories: options["discussions.categories"] ? { limit: options["discussions.categories.limit"] || Infinity } : null }, }, }) : null), @@ -690,6 +722,7 @@ ? ({ topics: { mode: options["topics.mode"], + type: {starred:"labels", labels:"labels", mastered:"icons", icons:"icons"}[options["topics.mode"]] || "labels", list: new Array(Number(options["topics.limit"]) || 20).fill(null).map(_ => ({ name: faker.lorem.words(2), description: faker.lorem.sentence(), @@ -770,7 +803,7 @@ ...(set.plugins.enabled.repositories ? ({ repositories: { - list: new Array(Number(options["repositories.featured"].split(",").length) - 1).fill(null).map((_, i) => ({ + list: new Array(Number(options["repositories.featured"].split(",").map(x => x.trim()).length)).fill(null).map((_, i) => ({ created: faker.date.past(), description: faker.lorem.sentence(), forkCount: faker.datatype.number(100), @@ -1058,7 +1091,7 @@ streak: { max: 30 + faker.datatype.number(20), current: faker.datatype.number(30) }, max: 10 + faker.datatype.number(40), average: faker.datatype.float(10), - svg: "(isometric calendar is not displayed in placeholder)", + svg: await staticPlaceholder(set.plugins.enabled.isocalendar, `isocalendar.${options["isocalendar.duration"]}.svg`), duration: options["isocalendar.duration"], }, }) @@ -1076,7 +1109,7 @@ ...(set.plugins.enabled.screenshot ? ({ screenshot: { - image: "data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", + image: "/.placeholders/screenshot.png", title: options["screenshot.title"], height: 440, width: 454, @@ -1087,7 +1120,7 @@ ...(set.plugins.enabled.skyline ? ({ skyline: { - animation: "data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", + animation: "/.placeholders/skyline.png", width: 454, height: 284, compatibility: false, diff --git a/source/app/web/statics/index.html b/source/app/web/statics/index.html index c310cbcd..ae5da4d1 100644 --- a/source/app/web/statics/index.html +++ b/source/app/web/statics/index.html @@ -1,3 +1,4 @@ + @@ -49,8 +50,8 @@
- - - {{ requests.remaining }} GitHub requests remaining + Remaining GitHub requests: + {{ requests.rest.remaining }} REST / {{ requests.graphql.remaining }} GraphQL Metrics are rendered by metrics.lecoq.io in preview mode. Any backend editions won't be reflected but client-side rendering can still be tested.
- Metrics cannot be generated because the following plugins are not available on this web instance: {{ unusable.join(", ") }} + The following plugins are not available on this web instance: {{ unusable.join(", ") }} +
+
+ This web instance has run out of GitHub API requests. + Please wait until {{ rlreset }} to generate metrics again.
@@ -101,7 +107,7 @@