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 @@
- 0)||(!requests.remaining) ? null : generate()">
-