feat(app/web): update ratelimit and placeholders (#826)

This commit is contained in:
Simon Lecoq authored and GitHub committed 2022-01-27 20:22:42 -05:00
1 parent 9f4e255c85
commit 3945af02d1
12 files changed
+3124 -28

No files matched your search

+24 -8
View File
@@ -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
} }
}) })
+8 -3
View File
@@ -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>
+10 -1
View File
@@ -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,
+24 -1
View File
@@ -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 {}
} }
}, },
}, },
+40 -7
View File
@@ -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,
+17 -8
View File
@@ -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>
File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 139 KiB

File diff suppressed because it is too large. Load diff

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

@@ -0,0 +1,34 @@
<div xmlns="http://www.w3.org/1999/xhtml" class="ct-chart" style="filter: grayscale(0.9)" >
<svg xmlns="http://www.w3.org/2000/svg" xmlns:ct="http://gionkunz.github.com/chartist-js/ct" width="480" height="160" class="ct-chart-line">
<!-- (optimized css) -->
<g class="ct-grids">
<line y1="125" y2="125" x1="50" x2="465" class="ct-grid ct-vertical"/>
<line y1="103" y2="103" x1="50" x2="465" class="ct-grid ct-vertical"/>
<line y1="81" y2="81" x1="50" x2="465" class="ct-grid ct-vertical"/>
<line y1="59" y2="59" x1="50" x2="465" class="ct-grid ct-vertical"/>
<line y1="37" y2="37" x1="50" x2="465" class="ct-grid ct-vertical"/>
<line y1="15" y2="15" x1="50" x2="465" class="ct-grid ct-vertical"/>
</g>
<g>
<g class="ct-series ct-series-a">
<path d="M50,125L50,60.881C50.732,59.554,51.464,58.019,52.196,56.899C52.928,55.779,53.66,53.995,54.392,53.995C55.123,53.995,55.855,54.611,56.587,54.611C57.319,54.611,58.051,50.761,58.783,50.761C59.515,50.761,60.247,51.289,60.979,51.289C61.711,51.289,62.443,50.57,63.175,49.837C63.907,49.104,64.638,45.915,65.37,45.316C66.102,44.717,66.834,44.456,67.566,44.194C68.298,43.932,69.03,43.589,69.762,43.589C70.494,43.589,71.226,44.7,71.958,44.7C72.69,44.7,73.422,44.282,74.153,44.282C74.885,44.282,75.617,47.34,76.349,47.34C77.081,47.34,77.813,45.061,78.545,44.7C79.277,44.339,80.009,44.018,80.741,44.018C81.473,44.018,82.205,44.59,82.937,44.59C83.668,44.59,84.4,43.077,85.132,43.028C85.864,42.979,86.596,42.997,87.328,42.951C88.06,42.905,88.792,42.599,89.524,42.225C90.256,41.851,90.988,40.05,91.72,39.75C92.451,39.45,93.183,39.463,93.915,39.2C94.647,38.938,95.379,37.814,96.111,37.814C96.843,37.814,97.575,41.158,98.307,41.158C99.039,41.158,99.771,41.115,100.503,41.037C101.235,40.959,101.966,37.198,102.698,37.198C103.43,37.198,104.162,37.305,104.894,37.44C105.626,37.575,106.358,38.694,107.09,38.694C107.822,38.694,108.554,37.209,109.286,37.209C110.018,37.209,110.75,37.745,111.481,38.32C112.213,38.895,112.945,42.17,113.677,42.17C114.409,42.17,115.141,39.216,115.873,39.112C116.605,39.008,117.337,38.947,118.069,38.947C118.801,38.947,119.533,39.825,120.265,39.86C120.996,39.895,121.728,39.915,122.46,39.915C123.192,39.915,123.924,39.915,124.656,39.915C125.388,39.915,126.12,39.728,126.852,39.728C127.584,39.728,128.316,44.272,129.048,45.305C129.78,46.338,130.511,46.774,131.243,47.45C131.975,48.126,132.707,49.375,133.439,49.375C134.171,49.375,134.903,49.1,135.635,49.1C136.367,49.1,137.099,51.377,137.831,51.377C138.563,51.377,139.295,50.491,140.026,50.057C140.758,49.623,141.49,49.184,142.222,48.77C142.954,48.356,143.686,47.571,144.418,47.571C145.15,47.571,145.882,48.01,146.614,48.308C147.346,48.606,148.078,49.441,148.81,49.441C149.541,49.441,150.273,48.553,151.005,48C151.737,47.447,152.469,46.652,153.201,46.042C153.933,45.432,154.665,44.368,155.397,44.326C156.129,44.284,156.861,44.26,157.593,44.26C158.325,44.26,159.056,44.282,159.788,44.282C160.52,44.282,161.252,41.578,161.984,40.663C162.716,39.748,163.448,38.452,164.18,38.452C164.912,38.452,165.644,39.561,166.376,40.113C167.108,40.665,167.84,41.763,168.571,41.763C169.303,41.763,170.035,38.14,170.767,38.133C171.499,38.126,172.231,38.122,172.963,38.122C173.695,38.122,174.427,38.594,175.159,38.716C175.891,38.838,176.623,38.98,177.354,38.98C178.086,38.98,178.818,38.65,179.55,38.65C180.282,38.65,181.014,38.943,181.746,39.288C182.478,39.633,183.21,42.038,183.942,42.038C184.674,42.038,185.406,40.498,186.138,40.498C186.869,40.498,187.601,55.117,188.333,55.117C189.065,55.117,189.797,52.07,190.529,52.07C191.261,52.07,191.993,55.502,192.725,55.502C193.457,55.502,194.189,52.496,194.921,51.146C195.653,49.796,196.384,47.631,197.116,47.362C197.848,47.093,198.58,47.2,199.312,46.911C200.044,46.623,200.776,36.208,201.508,36.208C202.24,36.208,202.972,37.606,203.704,38.826C204.436,40.046,205.168,44.903,205.899,44.903C206.631,44.903,207.363,42.785,208.095,42.785C208.827,42.785,209.559,52.592,210.291,52.592C211.023,52.592,211.755,50.477,212.487,49.754C213.219,49.032,213.951,48,214.683,48C215.414,48,216.146,50.274,216.878,51.729C217.61,53.184,218.342,56.994,219.074,56.994C219.806,56.994,220.538,55.799,221.27,55.799C222.002,55.799,222.734,56.16,223.466,56.16C224.198,56.16,224.929,56.069,225.661,56.069C226.393,56.069,227.125,58.692,227.857,58.692C228.589,58.692,229.321,53.71,230.053,52.697C230.785,51.684,231.517,51.329,232.249,50.662C232.981,49.995,233.713,49.336,234.444,48.693C235.176,48.05,235.908,46.801,236.64,46.801C237.372,46.801,238.104,48.006,238.836,48.649C239.568,49.292,240.3,50.667,241.032,50.667C241.764,50.667,242.496,49.648,243.228,48.748C243.959,47.848,244.691,44.205,245.423,44.205C246.155,44.205,246.887,45.797,247.619,46.009C248.351,46.221,249.083,46.394,249.815,46.394C250.547,46.394,251.279,45.468,252.011,44.722C252.743,43.976,253.474,41.336,254.206,41.336C254.938,41.336,255.67,41.359,256.402,41.4C257.134,41.441,257.866,43.513,258.598,43.589C259.33,43.665,260.062,43.634,260.794,43.71C261.526,43.786,262.257,45.525,262.989,45.525C263.721,45.525,264.453,41.95,265.185,41.95C265.917,41.95,266.649,44.579,267.381,44.579C268.113,44.579,268.845,42.926,269.577,41.587C270.309,40.248,271.041,35.787,271.772,35.482C272.504,35.177,273.236,35.089,273.968,34.987C274.7,34.885,275.432,34.878,276.164,34.767C276.896,34.656,277.628,34.074,278.36,34.074C279.092,34.074,279.824,41.07,280.556,41.07C281.287,41.07,282.019,40.914,282.751,40.696C283.483,40.478,284.215,38.089,284.947,38.089C285.679,38.089,286.411,38.754,287.143,38.754C287.875,38.754,288.607,38.611,289.339,38.535C290.071,38.459,290.802,38.298,291.534,38.298C292.266,38.298,292.998,40.596,293.73,40.69C294.462,40.783,295.194,40.839,295.926,40.839C296.658,40.839,297.39,39.176,298.122,38.87C298.854,38.564,299.586,38.624,300.317,38.271C301.049,37.9Line truncated
<path d="M50,60.881C50.732,59.554,51.464,58.019,52.196,56.899C52.928,55.779,53.66,53.995,54.392,53.995C55.123,53.995,55.855,54.611,56.587,54.611C57.319,54.611,58.051,50.761,58.783,50.761C59.515,50.761,60.247,51.289,60.979,51.289C61.711,51.289,62.443,50.57,63.175,49.837C63.907,49.104,64.638,45.915,65.37,45.316C66.102,44.717,66.834,44.456,67.566,44.194C68.298,43.932,69.03,43.589,69.762,43.589C70.494,43.589,71.226,44.7,71.958,44.7C72.69,44.7,73.422,44.282,74.153,44.282C74.885,44.282,75.617,47.34,76.349,47.34C77.081,47.34,77.813,45.061,78.545,44.7C79.277,44.339,80.009,44.018,80.741,44.018C81.473,44.018,82.205,44.59,82.937,44.59C83.668,44.59,84.4,43.077,85.132,43.028C85.864,42.979,86.596,42.997,87.328,42.951C88.06,42.905,88.792,42.599,89.524,42.225C90.256,41.851,90.988,40.05,91.72,39.75C92.451,39.45,93.183,39.463,93.915,39.2C94.647,38.938,95.379,37.814,96.111,37.814C96.843,37.814,97.575,41.158,98.307,41.158C99.039,41.158,99.771,41.115,100.503,41.037C101.235,40.959,101.966,37.198,102.698,37.198C103.43,37.198,104.162,37.305,104.894,37.44C105.626,37.575,106.358,38.694,107.09,38.694C107.822,38.694,108.554,37.209,109.286,37.209C110.018,37.209,110.75,37.745,111.481,38.32C112.213,38.895,112.945,42.17,113.677,42.17C114.409,42.17,115.141,39.216,115.873,39.112C116.605,39.008,117.337,38.947,118.069,38.947C118.801,38.947,119.533,39.825,120.265,39.86C120.996,39.895,121.728,39.915,122.46,39.915C123.192,39.915,123.924,39.915,124.656,39.915C125.388,39.915,126.12,39.728,126.852,39.728C127.584,39.728,128.316,44.272,129.048,45.305C129.78,46.338,130.511,46.774,131.243,47.45C131.975,48.126,132.707,49.375,133.439,49.375C134.171,49.375,134.903,49.1,135.635,49.1C136.367,49.1,137.099,51.377,137.831,51.377C138.563,51.377,139.295,50.491,140.026,50.057C140.758,49.623,141.49,49.184,142.222,48.77C142.954,48.356,143.686,47.571,144.418,47.571C145.15,47.571,145.882,48.01,146.614,48.308C147.346,48.606,148.078,49.441,148.81,49.441C149.541,49.441,150.273,48.553,151.005,48C151.737,47.447,152.469,46.652,153.201,46.042C153.933,45.432,154.665,44.368,155.397,44.326C156.129,44.284,156.861,44.26,157.593,44.26C158.325,44.26,159.056,44.282,159.788,44.282C160.52,44.282,161.252,41.578,161.984,40.663C162.716,39.748,163.448,38.452,164.18,38.452C164.912,38.452,165.644,39.561,166.376,40.113C167.108,40.665,167.84,41.763,168.571,41.763C169.303,41.763,170.035,38.14,170.767,38.133C171.499,38.126,172.231,38.122,172.963,38.122C173.695,38.122,174.427,38.594,175.159,38.716C175.891,38.838,176.623,38.98,177.354,38.98C178.086,38.98,178.818,38.65,179.55,38.65C180.282,38.65,181.014,38.943,181.746,39.288C182.478,39.633,183.21,42.038,183.942,42.038C184.674,42.038,185.406,40.498,186.138,40.498C186.869,40.498,187.601,55.117,188.333,55.117C189.065,55.117,189.797,52.07,190.529,52.07C191.261,52.07,191.993,55.502,192.725,55.502C193.457,55.502,194.189,52.496,194.921,51.146C195.653,49.796,196.384,47.631,197.116,47.362C197.848,47.093,198.58,47.2,199.312,46.911C200.044,46.623,200.776,36.208,201.508,36.208C202.24,36.208,202.972,37.606,203.704,38.826C204.436,40.046,205.168,44.903,205.899,44.903C206.631,44.903,207.363,42.785,208.095,42.785C208.827,42.785,209.559,52.592,210.291,52.592C211.023,52.592,211.755,50.477,212.487,49.754C213.219,49.032,213.951,48,214.683,48C215.414,48,216.146,50.274,216.878,51.729C217.61,53.184,218.342,56.994,219.074,56.994C219.806,56.994,220.538,55.799,221.27,55.799C222.002,55.799,222.734,56.16,223.466,56.16C224.198,56.16,224.929,56.069,225.661,56.069C226.393,56.069,227.125,58.692,227.857,58.692C228.589,58.692,229.321,53.71,230.053,52.697C230.785,51.684,231.517,51.329,232.249,50.662C232.981,49.995,233.713,49.336,234.444,48.693C235.176,48.05,235.908,46.801,236.64,46.801C237.372,46.801,238.104,48.006,238.836,48.649C239.568,49.292,240.3,50.667,241.032,50.667C241.764,50.667,242.496,49.648,243.228,48.748C243.959,47.848,244.691,44.205,245.423,44.205C246.155,44.205,246.887,45.797,247.619,46.009C248.351,46.221,249.083,46.394,249.815,46.394C250.547,46.394,251.279,45.468,252.011,44.722C252.743,43.976,253.474,41.336,254.206,41.336C254.938,41.336,255.67,41.359,256.402,41.4C257.134,41.441,257.866,43.513,258.598,43.589C259.33,43.665,260.062,43.634,260.794,43.71C261.526,43.786,262.257,45.525,262.989,45.525C263.721,45.525,264.453,41.95,265.185,41.95C265.917,41.95,266.649,44.579,267.381,44.579C268.113,44.579,268.845,42.926,269.577,41.587C270.309,40.248,271.041,35.787,271.772,35.482C272.504,35.177,273.236,35.089,273.968,34.987C274.7,34.885,275.432,34.878,276.164,34.767C276.896,34.656,277.628,34.074,278.36,34.074C279.092,34.074,279.824,41.07,280.556,41.07C281.287,41.07,282.019,40.914,282.751,40.696C283.483,40.478,284.215,38.089,284.947,38.089C285.679,38.089,286.411,38.754,287.143,38.754C287.875,38.754,288.607,38.611,289.339,38.535C290.071,38.459,290.802,38.298,291.534,38.298C292.266,38.298,292.998,40.596,293.73,40.69C294.462,40.783,295.194,40.839,295.926,40.839C296.658,40.839,297.39,39.176,298.122,38.87C298.854,38.564,299.586,38.624,300.317,38.271C301.049,37.917,301.Line truncated
</g>
</g>
<g class="ct-labels">
<text x="50" y="145" width="2.195767195767196" height="20" class="ct-label ct-horizontal ct-end">26/01, 09:00</text>
<text x="153.2010582010582" y="145" width="2.195767195767189" height="20" class="ct-label ct-horizontal ct-end">26/01, 12:55</text>
<text x="256.4021164021164" y="145" width="2.195767195767189" height="20" class="ct-label ct-horizontal ct-end">26/01, 17:05</text>
<text x="359.60317460317464" y="145" width="2.195767195767189" height="20" class="ct-label ct-horizontal ct-end">26/01, 21:00</text>
<text x="462.8042328042328" y="145" width="30" height="20" class="ct-label ct-horizontal ct-end">27/01, 00:55</text>
<text y="125" x="40" height="22" width="30" class="ct-label ct-vertical ct-start">880</text>
<text y="103" x="40" height="22" width="30" class="ct-label ct-vertical ct-start">900</text>
<text y="81" x="40" height="22" width="30" class="ct-label ct-vertical ct-start">920</text>
<text y="59" x="40" height="22" width="30" class="ct-label ct-vertical ct-start">940</text>
<text y="37" x="40" height="22" width="30" class="ct-label ct-vertical ct-start">960</text>
<text y="15" x="40" height="30" width="30" class="ct-label ct-vertical ct-start">980</text>
</g>
</svg>
<div class="ct-legend">
</div>
</div>
+1
View File
@@ -10,6 +10,7 @@
iframe { iframe {
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 70vh;
} }
a:hover { a:hover {