feat(app/web): update ratelimit and placeholders (#826)
This commit is contained in:
@@ -12,13 +12,13 @@ import presets from "../metrics/presets.mjs"
|
|||||||
import setup from "../metrics/setup.mjs"
|
import setup from "../metrics/setup.mjs"
|
||||||
|
|
||||||
/**App */
|
/**App */
|
||||||
export default async function({sandbox} = {}) {
|
export default async function({sandbox = false} = {}) {
|
||||||
//Load configuration settings
|
//Load configuration settings
|
||||||
const {conf, Plugins, Templates} = await setup({sandbox})
|
const {conf, Plugins, Templates} = await setup({sandbox})
|
||||||
//Sandbox mode
|
//Sandbox mode
|
||||||
if (sandbox) {
|
if (sandbox) {
|
||||||
console.debug("metrics/app > sandbox mode is specified, enabling advanced features")
|
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 {token, maxusers = 0, restricted = [], debug = false, cached = 30 * 60 * 1000, port = 3000, ratelimiter = null, plugins = null} = conf.settings
|
||||||
const mock = sandbox || conf.settings.mocked
|
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 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 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()}
|
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) {
|
if (!conf.settings.notoken) {
|
||||||
requests = (await rest.rateLimit.get()).data.rate
|
const refresh = async () => {
|
||||||
setInterval(async () => {
|
|
||||||
try {
|
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 {
|
catch {
|
||||||
console.debug("metrics/app > failed to update remaining requests")
|
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
|
//Web
|
||||||
app.get("/", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/index.html`))
|
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))
|
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`))
|
||||||
@@ -205,6 +218,9 @@ export default async function({sandbox} = {}) {
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
return res.status(500).send("Internal Server Error: failed to process metrics correctly")
|
return res.status(500).send("Internal Server Error: failed to process metrics correctly")
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
_requests_refresh = true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
//Metrics
|
//Metrics
|
||||||
@@ -309,8 +325,8 @@ export default async function({sandbox} = {}) {
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
//After rendering
|
//After rendering
|
||||||
|
|
||||||
solve?.()
|
solve?.()
|
||||||
|
_requests_refresh = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M11.5 7a4.499 4.499 0 11-8.998 0A4.499 4.499 0 0111.5 7zm-.82 4.74a6 6 0 111.06-1.06l3.04 3.04a.75.75 0 11-1.06 1.06l-3.04-3.04z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M11.5 7a4.499 4.499 0 11-8.998 0A4.499 4.499 0 0111.5 7zm-.82 4.74a6 6 0 111.06-1.06l3.04 3.04a.75.75 0 11-1.06 1.06l-3.04-3.04z"></path></svg>
|
||||||
Search a GitHub user
|
Search a GitHub user
|
||||||
</h2>
|
</h2>
|
||||||
<small :class="{'error-text':!requests.remaining}">{{ requests.remaining }} GitHub requests remaining</small>
|
<small :class="{'error-text':(!requests.rest.remaining)||(!requests.graphql.remaining)}">Remaining GitHub requests: {{ requests.rest.remaining }} REST / {{ requests.graphql.remaining }} GraphQL</small>
|
||||||
<small>Send feedback on <a href="https://github.com/lowlighter/metrics/discussions/229" target="_blank">GitHub discussions</a>!</small>
|
<small>Send feedback on <a href="https://github.com/lowlighter/metrics/discussions/229" target="_blank">GitHub discussions</a>!</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="inputs">
|
<div class="inputs">
|
||||||
@@ -42,6 +43,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
<small class="info">
|
<small class="info">
|
||||||
Display rankings, contributions, highlights, commits calendar, used languages and recent activity from any user account!
|
Display rankings, contributions, highlights, commits calendar, used languages and recent activity from any user account!
|
||||||
</small>
|
</small>
|
||||||
@@ -237,7 +242,7 @@
|
|||||||
<div class="chart-bars">
|
<div class="chart-bars">
|
||||||
<div class="entry" v-for="h in 24">
|
<div class="entry" v-for="h in 24">
|
||||||
<span class="value">{{ habits[h] }}</span>
|
<span class="value">{{ habits[h] }}</span>
|
||||||
<div class="bar" :style="{height:(habits[h]/habits.max)*150, backgroundColor:`var(--color-calendar-graph-day-L${Math.ceil((habits[h]/habits.max)/0.25)}-bg)`}"></div>
|
<div class="bar" :style="{height:`${((habits[h]||0)/(habits.max||1))*150}px`, backgroundColor:`var(--color-calendar-graph-day-L${Math.ceil(((habits[h]||0)/(habits.max||1))/0.25)}-bg)`}"></div>
|
||||||
<span class="label">{{ `${h}`.padStart(2, 0) }}</span>
|
<span class="label">{{ `${h}`.padStart(2, 0) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -369,6 +374,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.18"></script>
|
<script src="/about/.statics/script.js?v=3.19"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -93,6 +93,10 @@
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
this.pending = false
|
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
|
return this.metrics?.rendered.plugins.followup ?? null
|
||||||
},
|
},
|
||||||
habits() {
|
habits() {
|
||||||
|
console.log(this.metrics?.rendered.plugins.habits.commits.hours)
|
||||||
return this.metrics?.rendered.plugins.habits.commits.hours ?? null
|
return this.metrics?.rendered.plugins.habits.commits.hours ?? null
|
||||||
},
|
},
|
||||||
isocalendar() {
|
isocalendar() {
|
||||||
@@ -142,6 +147,10 @@
|
|||||||
preview() {
|
preview() {
|
||||||
return /-preview$/.test(this.version)
|
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 initialization
|
||||||
data: {
|
data: {
|
||||||
@@ -151,7 +160,7 @@
|
|||||||
embed: false,
|
embed: false,
|
||||||
localstorage: false,
|
localstorage: false,
|
||||||
searchable: 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",
|
palette: "light",
|
||||||
metrics: null,
|
metrics: null,
|
||||||
pending: false,
|
pending: false,
|
||||||
|
|||||||
@@ -90,11 +90,25 @@
|
|||||||
tab: "overview",
|
tab: "overview",
|
||||||
palette: "light",
|
palette: "light",
|
||||||
clipboard: null,
|
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(),
|
cached: new Map(),
|
||||||
config: Object.fromEntries(Object.entries(metadata.core.web).map(([key, { defaulted }]) => [key, defaulted])),
|
config: Object.fromEntries(Object.entries(metadata.core.web).map(([key, { defaulted }]) => [key, defaulted])),
|
||||||
metadata: Object.fromEntries(Object.entries(metadata).map(([key, { web }]) => [key, web])),
|
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: {
|
plugins: {
|
||||||
base: {},
|
base: {},
|
||||||
list: [],
|
list: [],
|
||||||
@@ -251,6 +265,11 @@
|
|||||||
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
|
||||||
methods: {
|
methods: {
|
||||||
@@ -299,6 +318,10 @@
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
this.generated.pending = false
|
this.generated.pending = false
|
||||||
|
try {
|
||||||
|
const { data: requests } = await axios.get("/.requests")
|
||||||
|
this.requests = requests
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,12 @@
|
|||||||
values.push(probability)
|
values.push(probability)
|
||||||
return values.sort((a, b) => b - a)
|
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
|
//Placeholder function
|
||||||
globalThis.placeholder = async function(set) {
|
globalThis.placeholder = async function(set) {
|
||||||
//Load templates informations
|
//Load templates informations
|
||||||
@@ -241,8 +247,21 @@
|
|||||||
? ({
|
? ({
|
||||||
notable: {
|
notable: {
|
||||||
contributions: new Array(2 + faker.datatype.number(2)).fill(null).map(_ => ({
|
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==",
|
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"],
|
comments: options["reactions.limit"],
|
||||||
details: options["reactions.details"],
|
details: options["reactions.details"].split(",").map(x => x.trim()),
|
||||||
days: options["reactions.days"],
|
days: options["reactions.days"],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -451,7 +470,7 @@
|
|||||||
...(set.plugins.enabled.stock
|
...(set.plugins.enabled.stock
|
||||||
? ({
|
? ({
|
||||||
stock: {
|
stock: {
|
||||||
chart: "(stock chart is not displayed in placeholder)",
|
chart: await staticPlaceholder(set.plugins.enabled.stock, "stock.svg"),
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
price: faker.datatype.number(10000) / 100,
|
price: faker.datatype.number(10000) / 100,
|
||||||
previous: faker.datatype.number(10000) / 100,
|
previous: faker.datatype.number(10000) / 100,
|
||||||
@@ -553,10 +572,12 @@
|
|||||||
music: {
|
music: {
|
||||||
provider: "(music provider)",
|
provider: "(music provider)",
|
||||||
mode: "Suggested tracks",
|
mode: "Suggested tracks",
|
||||||
|
played_at: options["music.played.at"],
|
||||||
tracks: new Array(Number(options["music.limit"])).fill(null).map(_ => ({
|
tracks: new Array(Number(options["music.limit"])).fill(null).map(_ => ({
|
||||||
name: faker.random.words(5),
|
name: faker.random.words(5),
|
||||||
artist: faker.random.words(),
|
artist: faker.random.words(),
|
||||||
artwork: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
|
artwork: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
|
||||||
|
played_at: options["music.played.at"] ? faker.date.recent() : null,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -578,6 +599,16 @@
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
: null),
|
: 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
|
//Pagespeed
|
||||||
...(set.plugins.enabled.pagespeed
|
...(set.plugins.enabled.pagespeed
|
||||||
? ({
|
? ({
|
||||||
@@ -666,6 +697,7 @@
|
|||||||
started: faker.datatype.number(1000),
|
started: faker.datatype.number(1000),
|
||||||
comments: faker.datatype.number(1000),
|
comments: faker.datatype.number(1000),
|
||||||
answers: faker.datatype.number(1000),
|
answers: faker.datatype.number(1000),
|
||||||
|
display: { categories: options["discussions.categories"] ? { limit: options["discussions.categories.limit"] || Infinity } : null },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
: null),
|
: null),
|
||||||
@@ -690,6 +722,7 @@
|
|||||||
? ({
|
? ({
|
||||||
topics: {
|
topics: {
|
||||||
mode: options["topics.mode"],
|
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(_ => ({
|
list: new Array(Number(options["topics.limit"]) || 20).fill(null).map(_ => ({
|
||||||
name: faker.lorem.words(2),
|
name: faker.lorem.words(2),
|
||||||
description: faker.lorem.sentence(),
|
description: faker.lorem.sentence(),
|
||||||
@@ -770,7 +803,7 @@
|
|||||||
...(set.plugins.enabled.repositories
|
...(set.plugins.enabled.repositories
|
||||||
? ({
|
? ({
|
||||||
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(),
|
created: faker.date.past(),
|
||||||
description: faker.lorem.sentence(),
|
description: faker.lorem.sentence(),
|
||||||
forkCount: faker.datatype.number(100),
|
forkCount: faker.datatype.number(100),
|
||||||
@@ -1058,7 +1091,7 @@
|
|||||||
streak: { max: 30 + faker.datatype.number(20), current: faker.datatype.number(30) },
|
streak: { max: 30 + faker.datatype.number(20), current: faker.datatype.number(30) },
|
||||||
max: 10 + faker.datatype.number(40),
|
max: 10 + faker.datatype.number(40),
|
||||||
average: faker.datatype.float(10),
|
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"],
|
duration: options["isocalendar.duration"],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1076,7 +1109,7 @@
|
|||||||
...(set.plugins.enabled.screenshot
|
...(set.plugins.enabled.screenshot
|
||||||
? ({
|
? ({
|
||||||
screenshot: {
|
screenshot: {
|
||||||
image: "data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
|
image: "/.placeholders/screenshot.png",
|
||||||
title: options["screenshot.title"],
|
title: options["screenshot.title"],
|
||||||
height: 440,
|
height: 440,
|
||||||
width: 454,
|
width: 454,
|
||||||
@@ -1087,7 +1120,7 @@
|
|||||||
...(set.plugins.enabled.skyline
|
...(set.plugins.enabled.skyline
|
||||||
? ({
|
? ({
|
||||||
skyline: {
|
skyline: {
|
||||||
animation: "data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
|
animation: "/.placeholders/skyline.png",
|
||||||
width: 454,
|
width: 454,
|
||||||
height: 284,
|
height: 284,
|
||||||
compatibility: false,
|
compatibility: false,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
@@ -49,8 +50,8 @@
|
|||||||
|
|
||||||
<div class="ui-avatar" :style="{backgroundImage:avatar ? `url(${avatar})` : 'none'}"></div>
|
<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.remaining) ? null : generate()">
|
<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.remaining)">
|
<button @click="generate" :disabled="(!user)||(generated.pending)||(unusable.length > 0)||(!requests.rest.remaining)||(!requests.graphql.remaining)">
|
||||||
<template v-if="generated.pending">
|
<template v-if="generated.pending">
|
||||||
Generating metrics<span class="loading"></span>
|
Generating metrics<span class="loading"></span>
|
||||||
</template>
|
</template>
|
||||||
@@ -58,13 +59,18 @@
|
|||||||
Generate your metrics!
|
Generate your metrics!
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
<small :class="{'error-text':!requests.remaining}">{{ requests.remaining }} GitHub requests remaining</small>
|
<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">
|
<small class="warning" v-if="preview">
|
||||||
Metrics are rendered by <a href="https://metrics.lecoq.io/">metrics.lecoq.io</a> in preview mode.
|
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.
|
Any backend editions won't be reflected but client-side rendering can still be tested.
|
||||||
</small>
|
</small>
|
||||||
<div class="warning" v-if="unusable.length">
|
<div class="warning" v-if="unusable.length">
|
||||||
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(", ") }}
|
||||||
|
</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>
|
||||||
|
|
||||||
<div class="configuration">
|
<div class="configuration">
|
||||||
@@ -101,7 +107,7 @@
|
|||||||
<template v-for="(input, key) in configure">
|
<template v-for="(input, key) in configure">
|
||||||
<b v-if="typeof input === 'string'">{{ input }}</b>
|
<b v-if="typeof input === 'string'">{{ input }}</b>
|
||||||
<label v-else class="option">
|
<label v-else class="option">
|
||||||
<i>{{ input.text }}</i>
|
<i>{{ input.text.split("\n")[0] }}</i>
|
||||||
<input type="checkbox" v-if="input.type === 'boolean'" v-model="plugins.options[key]" @change="mock">
|
<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">
|
<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">
|
<select v-else-if="input.type === 'select'" v-model="plugins.options[key]" @change="mock">
|
||||||
@@ -118,7 +124,7 @@
|
|||||||
<template v-for="{key, target} in [{key:'base', target:plugins.options}, {key:'core', target:config}]">
|
<template v-for="{key, target} in [{key:'base', target:plugins.options}, {key:'core', target:config}]">
|
||||||
<template v-for="(input, key) in metadata[key]">
|
<template v-for="(input, key) in metadata[key]">
|
||||||
<label class="option">
|
<label class="option">
|
||||||
<i>{{ input.text }}</i>
|
<i>{{ input.text.split("\n")[0] }}</i>
|
||||||
<input type="checkbox" v-if="input.type === 'boolean'" v-model="target[key]" @change="mock">
|
<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">
|
<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">
|
<select v-else-if="input.type === 'select'" v-model="target[key]" @change="mock">
|
||||||
@@ -140,6 +146,9 @@
|
|||||||
<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>
|
<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>
|
<span>{{ user }}</span><span class="slash">/</span>README<span class="md">.md</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="readme" v-if="tab in docs">
|
||||||
|
<a :href="docs[tab].link">{{ docs[tab].name }}</a>
|
||||||
|
</div>
|
||||||
<div class="readme">
|
<div class="readme">
|
||||||
<a href="https://github.com/lowlighter/metrics/discussions" target="_blank">Send feedback</a>
|
<a href="https://github.com/lowlighter/metrics/discussions" target="_blank">Send feedback</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,7 +201,7 @@
|
|||||||
<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/vue.prism.min.js"></script>
|
||||||
<script src="/.js/clipboard.min.js"></script>
|
<script src="/.js/clipboard.min.js"></script>
|
||||||
<script src="/.js/app.placeholder.js?v=3.18"></script>
|
<script src="/.js/app.placeholder.js?v=3.19"></script>
|
||||||
<script src="/.js/app.js?v=3.18"></script>
|
<script src="/.js/app.js?v=3.19"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
1964
source/app/web/statics/placeholders/isocalendar.full-year.svg
Normal file
1964
source/app/web/statics/placeholders/isocalendar.full-year.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 139 KiB |
1002
source/app/web/statics/placeholders/isocalendar.half-year.svg
Normal file
1002
source/app/web/statics/placeholders/isocalendar.half-year.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 70 KiB |
BIN
source/app/web/statics/placeholders/screenshot.png
Normal file
BIN
source/app/web/statics/placeholders/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
BIN
source/app/web/statics/placeholders/skyline.png
Normal file
BIN
source/app/web/statics/placeholders/skyline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
34
source/app/web/statics/placeholders/stock.svg
Normal file
34
source/app/web/statics/placeholders/stock.svg
Normal file
File diff suppressed because one or more lines are too long
@@ -10,6 +10,7 @@
|
|||||||
iframe {
|
iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 70vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
|
|||||||
Reference in New Issue
Block a user