mirror of
https://github.com/cupcakearmy/ora.git
synced 2024-12-21 23:56:31 +00:00
limits
This commit is contained in:
parent
ef2a02577e
commit
142685b306
@ -2,6 +2,8 @@
|
||||
|
||||
## Current
|
||||
|
||||
- d3 graph
|
||||
|
||||
## Backlog
|
||||
|
||||
- Max time for website -> block
|
||||
@ -9,3 +11,7 @@
|
||||
- Options
|
||||
- Dashboard
|
||||
- Better icon
|
||||
- Add footer
|
||||
- Build With ♥️
|
||||
- Github link
|
||||
- website link
|
||||
|
@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "parcel watch --no-hmr manifest.json src/dashboard/index.html",
|
||||
"build": "parcel build manifest.json src/dashboard/index.html",
|
||||
"build": "parcel build --no-content-hash --no-source-maps manifest.json src/dashboard/index.html",
|
||||
"launch": "web-ext run -s dist"
|
||||
},
|
||||
"browserslist": [
|
||||
@ -11,12 +11,14 @@
|
||||
"last 2 firefox versions"
|
||||
],
|
||||
"dependencies": {
|
||||
"d3": "^6.1.1",
|
||||
"dayjs": "^1.8.36",
|
||||
"faker": "^5.1.0",
|
||||
"lodash": "^4.17.20",
|
||||
"nedb": "^1.8.0",
|
||||
"nedb-promises": "^4.0.4",
|
||||
"spectre.css": "^0.5.9",
|
||||
"svelte-spa-router": "^2.2.0",
|
||||
"tailwindcss": "^1.8.10",
|
||||
"webextension-polyfill": "^0.6.0"
|
||||
},
|
||||
|
@ -1,106 +1,40 @@
|
||||
<script>
|
||||
import DateInput from './components/DateInput.svelte'
|
||||
// import Chart from './components/Chart.svelte'
|
||||
import Router, { link } from 'svelte-spa-router'
|
||||
|
||||
import dj from 'dayjs'
|
||||
import RelativeTime from 'dayjs/plugin/relativeTime'
|
||||
import Duration from 'dayjs/plugin/duration'
|
||||
|
||||
import Dev from './components/Dev.svelte'
|
||||
import RangeChooser from './components/RangeChooser.svelte'
|
||||
import Dashboard from './pages/Dashboard.svelte'
|
||||
import Limits from './pages/Limits.svelte'
|
||||
|
||||
import { onMount } from 'svelte'
|
||||
import dayjs from 'dayjs'
|
||||
dj.extend(Duration)
|
||||
dj.extend(RelativeTime)
|
||||
|
||||
import { data, countInGroup } from './lib'
|
||||
import { env } from 'process'
|
||||
const routes = {
|
||||
'/': Dashboard,
|
||||
|
||||
let top = 20
|
||||
let full = 50
|
||||
|
||||
let loading = true
|
||||
let init = false
|
||||
let counted
|
||||
|
||||
let start
|
||||
let end
|
||||
let topData = {
|
||||
labels: [],
|
||||
data: [],
|
||||
'/limits': Limits,
|
||||
}
|
||||
|
||||
async function calculate() {
|
||||
try {
|
||||
loading = true
|
||||
const logs = await data({
|
||||
start,
|
||||
end: dayjs(end).endOf('day'),
|
||||
})
|
||||
counted = countInGroup(logs)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
// const onlyTop = counted.slice(0, top)
|
||||
// console.log(onlyTop)
|
||||
// topData = {
|
||||
// labels: onlyTop.map((n) => n[0]),
|
||||
// data: onlyTop.map((n) => n[1] / 1000),
|
||||
// }
|
||||
}
|
||||
|
||||
$: if (init) {
|
||||
start, end
|
||||
calculate()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
setTimeout(() => {
|
||||
init = true
|
||||
}, 25)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.top {
|
||||
main {
|
||||
padding: 1em;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
max-width: 50em;
|
||||
}
|
||||
|
||||
.link {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-right: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Dev />
|
||||
<div class="top rounded">
|
||||
<h1 class="text-6xl mb-4">Ora</h1>
|
||||
<div class="flex justify-end items-center mb-2">
|
||||
<h3>Time Range</h3>
|
||||
<RangeChooser bind:start bind:end />
|
||||
<main>
|
||||
<div class="mb-8">
|
||||
<a href="../options/index.html"><button class="btn">Options</button></a>
|
||||
<a use:link={'/'}><button class="btn">Dashboard</button></a>
|
||||
<a use:link={'/limits'}><button class="btn">Limits</button></a>
|
||||
</div>
|
||||
{#if loading}
|
||||
<div class="loading loading-lg" />
|
||||
{:else if counted}
|
||||
<h2 class="text-2xl">Top {top}</h2>
|
||||
<b>Chart</b>
|
||||
<h2 class="text-2xl mt-4">Top {full}</h2>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Time Spent</th>
|
||||
<th>Host</th>
|
||||
</tr>
|
||||
{#each counted.slice(0, 100) as { host, total, human }}
|
||||
<tr>
|
||||
<td>{human}</td>
|
||||
<td class="link"><a href={'https://' + host}>{host}</a></td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Router {routes} />
|
||||
</main>
|
||||
|
111
src/dashboard/components/Chart.svelte
Normal file
111
src/dashboard/components/Chart.svelte
Normal file
@ -0,0 +1,111 @@
|
||||
<script>
|
||||
import * as d3 from 'd3'
|
||||
import { map, min, max } from 'lodash'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
let wrapper
|
||||
|
||||
export let data = [
|
||||
// { lang: 'ts', popularity: 10 },
|
||||
// { lang: 'js', popularity: 7 },
|
||||
// { lang: 'py', popularity: 9 },
|
||||
// { lang: 'rs', popularity: 8 },
|
||||
|
||||
// { year: 2018, value: 8 },
|
||||
// { year: 2019, value: 9 },
|
||||
// { year: 2020, value: 3 },
|
||||
|
||||
{ cat: 'Phillip', value: 10 },
|
||||
{ cat: 'Rita', value: 12 },
|
||||
{ cat: 'Tom', value: 20 },
|
||||
{ cat: 'Oscar', value: 19 },
|
||||
{ cat: 'Lulu', value: 8 },
|
||||
{ cat: 'Keko', value: 14 },
|
||||
{ cat: 'Lena', value: 9 },
|
||||
]
|
||||
|
||||
onMount(async () => {
|
||||
// Dynamic left padding depending on the labels
|
||||
const longestKey = max(map(data, (d) => d.name.length))
|
||||
const mt = Math.min(longestKey * 6, 120)
|
||||
const margin = { left: mt, top: 50, bottom: 50, right: 50 }
|
||||
const styles = window.getComputedStyle(wrapper)
|
||||
const barHeight = 20
|
||||
const width = parseInt(styles.width)
|
||||
const height = Math.ceil(data.length * 1.5 * barHeight)
|
||||
|
||||
const svg = d3.select(wrapper).attr('viewBox', [0, 0, width, height])
|
||||
|
||||
const yAxis = (g) =>
|
||||
g.attr('transform', `translate(${margin.left},0)`).call(
|
||||
d3
|
||||
.axisLeft(y)
|
||||
.tickFormat((i) => data[i].name)
|
||||
.tickSizeOuter(0)
|
||||
)
|
||||
const xAxis = (g) =>
|
||||
g
|
||||
.attr('transform', `translate(0,${margin.top})`)
|
||||
.call(d3.axisTop(x).ticks(width / 100, 's'))
|
||||
.call((g) => g.select('.domain').remove())
|
||||
|
||||
const y = d3
|
||||
.scaleBand()
|
||||
.domain(d3.range(data.length))
|
||||
.rangeRound([margin.bottom, height - margin.top])
|
||||
.padding(0.2)
|
||||
const x = d3
|
||||
.scaleLinear()
|
||||
.domain([0, d3.max(data, (d) => d.value)])
|
||||
.range([margin.left, width - margin.right])
|
||||
|
||||
const format = x.tickFormat(20, 's')
|
||||
|
||||
// Bars
|
||||
svg
|
||||
.append('g')
|
||||
.attr('fill', 'steelblue')
|
||||
.selectAll('rect')
|
||||
.data(data)
|
||||
.join('rect')
|
||||
.attr('x', x(0))
|
||||
.attr('y', (d, i) => y(i))
|
||||
.attr('width', (d) => x(d.value) - x(0))
|
||||
.attr('height', y.bandwidth())
|
||||
|
||||
svg
|
||||
.append('g')
|
||||
.attr('fill', 'white')
|
||||
.attr('text-anchor', 'end')
|
||||
.attr('font-family', 'sans-serif')
|
||||
.attr('font-size', 12)
|
||||
.selectAll('text')
|
||||
.data(data)
|
||||
.join('text')
|
||||
.attr('x', (d) => x(d.value))
|
||||
.attr('y', (d, i) => y(i) + y.bandwidth() / 2)
|
||||
.attr('dy', '0.35em')
|
||||
.attr('dx', -4)
|
||||
.text((d) => d.human)
|
||||
.call((text) =>
|
||||
text
|
||||
.filter((d) => x(d.value) - x(0) < d.human.length * 7) // short bars
|
||||
.attr('dx', +4)
|
||||
.attr('fill', 'black')
|
||||
.attr('text-anchor', 'start')
|
||||
)
|
||||
|
||||
svg.append('g').call(xAxis)
|
||||
|
||||
svg.append('g').call(yAxis)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
svg {
|
||||
width: 100%;
|
||||
/* height: 25em; */
|
||||
}
|
||||
</style>
|
||||
|
||||
<svg bind:this={wrapper} preserveAspectRatio="xMidYMid meet" />
|
@ -1,115 +0,0 @@
|
||||
<script>
|
||||
import * as am4core from '@amcharts/amcharts4/core'
|
||||
import * as am4charts from '@amcharts/amcharts4/charts'
|
||||
import am4themes_frozen from '@amcharts/amcharts4/themes/frozen'
|
||||
import am4themes_animated from '@amcharts/amcharts4/themes/animated'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
/* Chart code */
|
||||
// Themes begin
|
||||
am4core.useTheme(am4themes_frozen)
|
||||
am4core.useTheme(am4themes_animated)
|
||||
|
||||
let el
|
||||
let chart
|
||||
|
||||
export let data = [
|
||||
{
|
||||
network: 'Facebook',
|
||||
MAU: 2255250000,
|
||||
},
|
||||
{
|
||||
network: 'Google+',
|
||||
MAU: 430000000,
|
||||
},
|
||||
{
|
||||
network: 'Instagram',
|
||||
MAU: 1000000000,
|
||||
},
|
||||
{
|
||||
network: 'Pinterest',
|
||||
MAU: 246500000,
|
||||
},
|
||||
{
|
||||
network: 'Reddit',
|
||||
MAU: 355000000,
|
||||
},
|
||||
{
|
||||
network: 'TikTok',
|
||||
MAU: 500000000,
|
||||
},
|
||||
{
|
||||
network: 'Tumblr',
|
||||
MAU: 624000000,
|
||||
},
|
||||
{
|
||||
network: 'Twitter',
|
||||
MAU: 329500000,
|
||||
},
|
||||
{
|
||||
network: 'WeChat',
|
||||
MAU: 1000000000,
|
||||
},
|
||||
{
|
||||
network: 'Weibo',
|
||||
MAU: 431000000,
|
||||
},
|
||||
{
|
||||
network: 'Whatsapp',
|
||||
MAU: 1433333333,
|
||||
},
|
||||
{
|
||||
network: 'YouTube',
|
||||
MAU: 1900000000,
|
||||
},
|
||||
]
|
||||
|
||||
$: if (chart) {
|
||||
chart.data = data
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
chart = am4core.create(el, am4charts.XYChart)
|
||||
|
||||
let categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis())
|
||||
categoryAxis.renderer.grid.template.location = 0
|
||||
categoryAxis.dataFields.category = 'host'
|
||||
categoryAxis.renderer.minGridDistance = 1
|
||||
categoryAxis.renderer.inversed = true
|
||||
categoryAxis.renderer.grid.template.disabled = true
|
||||
|
||||
let valueAxis = chart.xAxes.push(new am4charts.ValueAxis())
|
||||
valueAxis.min = 0
|
||||
|
||||
let series = chart.series.push(new am4charts.ColumnSeries())
|
||||
series.dataFields.categoryY = 'host'
|
||||
series.dataFields.valueX = 'total'
|
||||
series.columns.template.strokeOpacity = 0
|
||||
series.columns.template.column.cornerRadiusBottomRight = 5
|
||||
series.columns.template.column.cornerRadiusTopRight = 5
|
||||
|
||||
let labelBullet = series.bullets.push(new am4charts.LabelBullet())
|
||||
labelBullet.label.horizontalCenter = 'left'
|
||||
labelBullet.label.dx = 10
|
||||
labelBullet.label.text = '{values.valueX.workingValue}'
|
||||
// labelBullet.label.text = "{values.valueX.workingValue.formatNumber('#.0as')}"
|
||||
labelBullet.locationX = 1
|
||||
|
||||
// as by default columns of the same series are of the same color, we add adapter which takes colors from chart.colors color set
|
||||
series.columns.template.adapter.add('fill', function (fill, target) {
|
||||
return chart.colors.getIndex(target.dataItem.index)
|
||||
})
|
||||
|
||||
categoryAxis.sortBySeries = series
|
||||
chart.data = data
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div {
|
||||
width: 100%;
|
||||
height: 32em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div bind:this={el}>chart</div>
|
@ -1,68 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
import Chart from 'chart.js'
|
||||
import palette from 'google-palette'
|
||||
|
||||
Chart.defaults.global.legend.display = false
|
||||
|
||||
export let type = 'horizontalBar'
|
||||
export let data = {
|
||||
labels: [],
|
||||
data: [],
|
||||
}
|
||||
export let options = {
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
// type: 'logarithmic',
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
let ctx
|
||||
let mounted
|
||||
|
||||
function draw() {
|
||||
const backgroundColor = palette('rainbow', data.data.length).map((color) => '#' + color + '88')
|
||||
|
||||
new Chart(ctx, {
|
||||
type,
|
||||
options,
|
||||
data: {
|
||||
labels: data.labels,
|
||||
datasets: [
|
||||
{
|
||||
data: data.data,
|
||||
// backgroundColor: backgroundColor,
|
||||
backgroundColor: '#dddddd',
|
||||
borderColor: '#000000',
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
$: if (mounted) {
|
||||
data
|
||||
draw()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
mounted = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 20em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<canvas bind:this={ctx} />
|
@ -3,7 +3,7 @@
|
||||
import day from 'dayjs'
|
||||
import { range, random } from 'lodash'
|
||||
|
||||
import { insertLog, normalizeTimestamp } from '../../shared/db'
|
||||
import { insertLog, normalizeTimestamp, clear as clearDB } from '../../shared/db'
|
||||
|
||||
let loading = false
|
||||
|
||||
@ -30,14 +30,22 @@
|
||||
async function clear() {
|
||||
try {
|
||||
loading = true
|
||||
await Logs.remove({}, { multi: true })
|
||||
await clearDB()
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="p-2">
|
||||
<button class="btn" class:loading disabled={loading} on:click={fill}>Add Random Data</button>
|
||||
<button class="btn btn-error" class:loading disabled={loading} on:click={clear}>Delete data</button>
|
||||
<button class="btn btn-sm" class:loading disabled={loading} on:click={fill}>Add Random Data</button>
|
||||
<button class="btn btn-sm btn-error" class:loading disabled={loading} on:click={clear}>Delete data</button>
|
||||
</div>
|
||||
|
10
src/dashboard/components/Rules.svelte
Normal file
10
src/dashboard/components/Rules.svelte
Normal file
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
import dj from 'dayjs'
|
||||
import { Logs } from '../../shared/db.js'
|
||||
|
||||
export let rules = []
|
||||
</script>
|
||||
|
||||
{#each rules as rule}
|
||||
<div>{dj.duration(...rule.limit).humanize()} / {dj.duration(...rule.every).humanize()}</div>
|
||||
{/each}
|
80
src/dashboard/components/RulesEditor.svelte
Normal file
80
src/dashboard/components/RulesEditor.svelte
Normal file
@ -0,0 +1,80 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
import { Limits } from '../../shared/db'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const init = { limit: ['1', 'h'], every: [1, 'd'] }
|
||||
|
||||
export let limit = null
|
||||
$: active = limit !== null
|
||||
|
||||
function add() {
|
||||
limit.rules = [...limit.rules, cloneDeep(init)]
|
||||
}
|
||||
|
||||
function del(i) {
|
||||
return () => (limit.rules = limit.rules.filter((_, n) => n !== i))
|
||||
}
|
||||
|
||||
function close() {
|
||||
limit = null
|
||||
}
|
||||
|
||||
async function save() {
|
||||
await Limits.update({ host: limit.host }, limit, { upsert: true })
|
||||
dispatch('update')
|
||||
close()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class:active class="modal">
|
||||
<a on:click={close} class="modal-overlay" aria-label="Close" />
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<a on:click={close} class="btn btn-clear float-right" aria-label="Close" />
|
||||
<div class="modal-title h5">{limit?._id ? 'Edit limit' : 'Create new limit'}</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="content">
|
||||
{#if limit}
|
||||
<label class="form-label">
|
||||
Host <input type="text" class="form-input" placeholder="google.com" bind:value={limit.host} />
|
||||
</label>
|
||||
|
||||
<div class="form-label">Rules</div>
|
||||
{#each limit.rules as { limit, every }, i}
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" class="form-input" placeholder="1" bind:value={limit[0]} />
|
||||
<select class="form-select" bind:value={limit[1]}>
|
||||
<option value="m">Minutes</option>
|
||||
<option value="h">Hours</option>
|
||||
<option value="d">Days</option>
|
||||
<option value="w">Weeks</option>
|
||||
<option value="M">Months</option>
|
||||
</select>
|
||||
<span class="input-group-addon">every</span>
|
||||
<input type="text" class="form-input" bind:value={every[0]} />
|
||||
<select class="form-select" bind:value={every[1]}>
|
||||
<option value="m">Minutes</option>
|
||||
<option value="h">Hours</option>
|
||||
<option value="d">Days</option>
|
||||
<option value="w">Weeks</option>
|
||||
<option value="M">Months</option>
|
||||
</select>
|
||||
<button class="btn btn-error input-group-btn" on:click={del(i)}>X</button>
|
||||
</div>
|
||||
{/each}
|
||||
<button class="btn" on:click={add}>Add</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div>
|
||||
<button on:click={close} class="btn">Cancel</button>
|
||||
<button on:click={save} class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,10 +1,5 @@
|
||||
import { each, groupBy, orderBy } from 'lodash'
|
||||
import dj from 'dayjs'
|
||||
import RelativeTime from 'dayjs/plugin/relativeTime'
|
||||
import Duration from 'dayjs/plugin/duration'
|
||||
|
||||
dj.extend(Duration)
|
||||
dj.extend(RelativeTime)
|
||||
|
||||
import { Logs } from '../shared/db'
|
||||
|
||||
@ -32,3 +27,9 @@ export function countInGroup(grouped) {
|
||||
})
|
||||
return orderBy(counted, 'total', 'desc')
|
||||
}
|
||||
|
||||
export function longPress(node, fn) {
|
||||
let timeout
|
||||
node.addEventListener('mousedown', () => (timeout = setTimeout(fn, 500)), false)
|
||||
node.addEventListener('mouseup', () => clearTimeout(timeout), false)
|
||||
}
|
||||
|
73
src/dashboard/pages/Dashboard.svelte
Normal file
73
src/dashboard/pages/Dashboard.svelte
Normal file
@ -0,0 +1,73 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import DateInput from '../components/DateInput.svelte'
|
||||
import Chart from '../components/Chart.svelte'
|
||||
import RangeChooser from '../components/RangeChooser.svelte'
|
||||
|
||||
import { data, countInGroup } from '../lib'
|
||||
|
||||
let top = 15
|
||||
let full = 50
|
||||
|
||||
let loading = true
|
||||
let init = false
|
||||
let counted = []
|
||||
|
||||
let start
|
||||
let end
|
||||
|
||||
async function calculate() {
|
||||
try {
|
||||
loading = true
|
||||
const logs = await data({
|
||||
start,
|
||||
end: dayjs(end).endOf('day'),
|
||||
})
|
||||
counted = countInGroup(logs)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
$: if (init) {
|
||||
start, end
|
||||
calculate()
|
||||
}
|
||||
|
||||
$: topData = counted.slice(0, top).map(({ total, host, human }) => ({ value: total, name: host, human }))
|
||||
|
||||
onMount(() => {
|
||||
setTimeout(() => {
|
||||
init = true
|
||||
}, 25)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl">Dashboard</h2>
|
||||
<RangeChooser bind:start bind:end />
|
||||
</div>
|
||||
{#if loading}
|
||||
<div class="loading loading-lg" />
|
||||
{:else if counted}
|
||||
<h2 class="text-lg">Top {top}</h2>
|
||||
<Chart data={topData} />
|
||||
<h2 class="text-lg mt-4">Top {full}</h2>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Time Spent</th>
|
||||
<th>Host</th>
|
||||
</tr>
|
||||
{#each counted.slice(0, 100) as { host, total, human }}
|
||||
<tr>
|
||||
<td>{human}</td>
|
||||
<td class="link"><a href={'https://' + host}>{host}</a></td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
{/if}
|
72
src/dashboard/pages/Limits.svelte
Normal file
72
src/dashboard/pages/Limits.svelte
Normal file
@ -0,0 +1,72 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
import RulesEditor from '../components/RulesEditor.svelte'
|
||||
import Rules from '../components/Rules.svelte'
|
||||
|
||||
import { Limits } from '../../shared/db.js'
|
||||
import { longPress } from '../lib'
|
||||
|
||||
let limits = null
|
||||
let limit = null
|
||||
|
||||
function create() {
|
||||
limit = { host: '', rules: [] }
|
||||
}
|
||||
|
||||
function edit(id) {
|
||||
limit = limits.find((limit) => limit._id === id)
|
||||
}
|
||||
|
||||
async function load() {
|
||||
limits = await Limits.find()
|
||||
}
|
||||
|
||||
async function del(id) {
|
||||
await Limits.remove({ _id: id })
|
||||
await load()
|
||||
}
|
||||
|
||||
onMount(load)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
|
||||
<RulesEditor bind:limit on:update={load} />
|
||||
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl">Limits</h2>
|
||||
<button class="btn btn-primary" on:click={create}>New Rule</button>
|
||||
</div>
|
||||
|
||||
{#if Array.isArray(limits)}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Host</th>
|
||||
<th>Rules</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
{#each limits as { host, rules, _id }}
|
||||
<tr>
|
||||
<td>{host}</td>
|
||||
<td>
|
||||
<Rules {rules} />
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-primary" on:click={() => edit(_id)}>Edit</button>
|
||||
<button class="btn btn-sm btn-error tooltip" data-tooltip="Hold to delete" use:longPress={() => del(_id)}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
{:else}
|
||||
<div class="loading loading-lg" />
|
||||
{/if}
|
@ -6,6 +6,15 @@ export const Logs = NeDB.create({
|
||||
autoload: true,
|
||||
})
|
||||
|
||||
export const Limits = NeDB.create({
|
||||
filename: 'limits.db',
|
||||
autoload: true,
|
||||
})
|
||||
|
||||
export function clear() {
|
||||
return Promise.all([Logs.remove({}, { multi: true }), Limits.remove({}, { multi: true })])
|
||||
}
|
||||
|
||||
export function normalizeTimestamp(timestamp) {
|
||||
// Normalize every dato to 15 minutes
|
||||
const t = day(timestamp)
|
||||
|
274
yarn.lock
274
yarn.lock
@ -2135,6 +2135,11 @@ command-exists@^1.2.6:
|
||||
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
|
||||
integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
|
||||
|
||||
commander@2, commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.3.0, commander@^2.6.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
|
||||
@ -2142,11 +2147,6 @@ commander@2.9.0:
|
||||
dependencies:
|
||||
graceful-readlink ">= 1.0.0"
|
||||
|
||||
commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.3.0, commander@^2.6.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||
@ -2523,6 +2523,246 @@ cssstyle@^1.1.1:
|
||||
dependencies:
|
||||
cssom "0.3.x"
|
||||
|
||||
"d3-array@1.2.0 - 2", d3-array@2, d3-array@>=2.5:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.7.1.tgz#b1f56065e9aba1ef6f0d0c8c9390b65421593352"
|
||||
integrity sha512-dYWhEvg1L2+osFsSqNHpXaPQNugLT4JfyvbLE046I2PDcgYGFYc0w24GSJwbmcjjZYOPC3PNP2S782bWUM967Q==
|
||||
|
||||
d3-axis@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-2.0.0.tgz#40aebb65626ffe6d95e9441fbf9194274b328a8b"
|
||||
integrity sha512-9nzB0uePtb+u9+dWir+HTuEAKJOEUYJoEwbJPsZ1B4K3iZUgzJcSENQ05Nj7S4CIfbZZ8/jQGoUzGKFznBhiiQ==
|
||||
|
||||
d3-brush@2:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-2.1.0.tgz#adadfbb104e8937af142e9a6e2028326f0471065"
|
||||
integrity sha512-cHLLAFatBATyIKqZOkk/mDHUbzne2B3ZwxkzMHvFTCZCmLaXDpZRihQSn8UNXTkGD/3lb/W2sQz0etAftmHMJQ==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 2"
|
||||
d3-drag "2"
|
||||
d3-interpolate "1 - 2"
|
||||
d3-selection "2"
|
||||
d3-transition "2"
|
||||
|
||||
d3-chord@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-2.0.0.tgz#32491b5665391180560f738e5c1ccd1e3c47ebae"
|
||||
integrity sha512-D5PZb7EDsRNdGU4SsjQyKhja8Zgu+SHZfUSO5Ls8Wsn+jsAKUUGkcshLxMg9HDFxG3KqavGWaWkJ8EpU8ojuig==
|
||||
dependencies:
|
||||
d3-path "1 - 2"
|
||||
|
||||
"d3-color@1 - 2", d3-color@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e"
|
||||
integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==
|
||||
|
||||
d3-contour@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-2.0.0.tgz#80ee834988563e3bea9d99ddde72c0f8c089ea40"
|
||||
integrity sha512-9unAtvIaNk06UwqBmvsdHX7CZ+NPDZnn8TtNH1myW93pWJkhsV25JcgnYAu0Ck5Veb1DHiCv++Ic5uvJ+h50JA==
|
||||
dependencies:
|
||||
d3-array "2"
|
||||
|
||||
d3-delaunay@5:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-5.3.0.tgz#b47f05c38f854a4e7b3cea80e0bb12e57398772d"
|
||||
integrity sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==
|
||||
dependencies:
|
||||
delaunator "4"
|
||||
|
||||
"d3-dispatch@1 - 2", d3-dispatch@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-2.0.0.tgz#8a18e16f76dd3fcaef42163c97b926aa9b55e7cf"
|
||||
integrity sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==
|
||||
|
||||
d3-drag@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-2.0.0.tgz#9eaf046ce9ed1c25c88661911c1d5a4d8eb7ea6d"
|
||||
integrity sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 2"
|
||||
d3-selection "2"
|
||||
|
||||
"d3-dsv@1 - 2", d3-dsv@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-2.0.0.tgz#b37b194b6df42da513a120d913ad1be22b5fe7c5"
|
||||
integrity sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==
|
||||
dependencies:
|
||||
commander "2"
|
||||
iconv-lite "0.4"
|
||||
rw "1"
|
||||
|
||||
"d3-ease@1 - 2", d3-ease@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-2.0.0.tgz#fd1762bfca00dae4bacea504b1d628ff290ac563"
|
||||
integrity sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==
|
||||
|
||||
d3-fetch@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-2.0.0.tgz#ecd7ef2128d9847a3b41b548fec80918d645c064"
|
||||
integrity sha512-TkYv/hjXgCryBeNKiclrwqZH7Nb+GaOwo3Neg24ZVWA3MKB+Rd+BY84Nh6tmNEMcjUik1CSUWjXYndmeO6F7sw==
|
||||
dependencies:
|
||||
d3-dsv "1 - 2"
|
||||
|
||||
d3-force@2:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-2.1.1.tgz#f20ccbf1e6c9e80add1926f09b51f686a8bc0937"
|
||||
integrity sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 2"
|
||||
d3-quadtree "1 - 2"
|
||||
d3-timer "1 - 2"
|
||||
|
||||
"d3-format@1 - 2", d3-format@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767"
|
||||
integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==
|
||||
|
||||
d3-geo@2:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-2.0.1.tgz#2437fdfed3fe3aba2812bd8f30609cac83a7ee39"
|
||||
integrity sha512-M6yzGbFRfxzNrVhxDJXzJqSLQ90q1cCyb3EWFZ1LF4eWOBYxFypw7I/NFVBNXKNqxv1bqLathhYvdJ6DC+th3A==
|
||||
dependencies:
|
||||
d3-array ">=2.5"
|
||||
|
||||
d3-hierarchy@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz#dab88a58ca3e7a1bc6cab390e89667fcc6d20218"
|
||||
integrity sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw==
|
||||
|
||||
"d3-interpolate@1 - 2", "d3-interpolate@1.2.0 - 2", d3-interpolate@2:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163"
|
||||
integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==
|
||||
dependencies:
|
||||
d3-color "1 - 2"
|
||||
|
||||
"d3-path@1 - 2", d3-path@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-2.0.0.tgz#55d86ac131a0548adae241eebfb56b4582dd09d8"
|
||||
integrity sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==
|
||||
|
||||
d3-polygon@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-2.0.0.tgz#13608ef042fbec625ba1598327564f03c0396d8e"
|
||||
integrity sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ==
|
||||
|
||||
"d3-quadtree@1 - 2", d3-quadtree@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-2.0.0.tgz#edbad045cef88701f6fee3aee8e93fb332d30f9d"
|
||||
integrity sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==
|
||||
|
||||
d3-random@2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-2.2.2.tgz#5eebd209ef4e45a2b362b019c1fb21c2c98cbb6e"
|
||||
integrity sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==
|
||||
|
||||
d3-scale-chromatic@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz#c13f3af86685ff91323dc2f0ebd2dabbd72d8bab"
|
||||
integrity sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==
|
||||
dependencies:
|
||||
d3-color "1 - 2"
|
||||
d3-interpolate "1 - 2"
|
||||
|
||||
d3-scale@3:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.2.2.tgz#36d4cbc94dc38bbb5bd91ba5eddb44c6d19bad3e"
|
||||
integrity sha512-3Mvi5HfqPFq0nlyeFlkskGjeqrR/790pINMHc4RXKJ2E6FraTd3juaRIRZZHyMAbi3LjAMW0EH4FB1WgoGyeXg==
|
||||
dependencies:
|
||||
d3-array "1.2.0 - 2"
|
||||
d3-format "1 - 2"
|
||||
d3-interpolate "1.2.0 - 2"
|
||||
d3-time "1 - 2"
|
||||
d3-time-format "2 - 3"
|
||||
|
||||
d3-selection@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-2.0.0.tgz#94a11638ea2141b7565f883780dabc7ef6a61066"
|
||||
integrity sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA==
|
||||
|
||||
d3-shape@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-2.0.0.tgz#2331b62fa784a2a1daac47a7233cfd69301381fd"
|
||||
integrity sha512-djpGlA779ua+rImicYyyjnOjeubyhql1Jyn1HK0bTyawuH76UQRWXd+pftr67H6Fa8hSwetkgb/0id3agKWykw==
|
||||
dependencies:
|
||||
d3-path "1 - 2"
|
||||
|
||||
"d3-time-format@2 - 3", d3-time-format@3:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6"
|
||||
integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==
|
||||
dependencies:
|
||||
d3-time "1 - 2"
|
||||
|
||||
"d3-time@1 - 2", d3-time@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.0.0.tgz#ad7c127d17c67bd57a4c61f3eaecb81108b1e0ab"
|
||||
integrity sha512-2mvhstTFcMvwStWd9Tj3e6CEqtOivtD8AUiHT8ido/xmzrI9ijrUUihZ6nHuf/vsScRBonagOdj0Vv+SEL5G3Q==
|
||||
|
||||
"d3-timer@1 - 2", d3-timer@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-2.0.0.tgz#055edb1d170cfe31ab2da8968deee940b56623e6"
|
||||
integrity sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==
|
||||
|
||||
d3-transition@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-2.0.0.tgz#366ef70c22ef88d1e34105f507516991a291c94c"
|
||||
integrity sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog==
|
||||
dependencies:
|
||||
d3-color "1 - 2"
|
||||
d3-dispatch "1 - 2"
|
||||
d3-ease "1 - 2"
|
||||
d3-interpolate "1 - 2"
|
||||
d3-timer "1 - 2"
|
||||
|
||||
d3-zoom@2:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-2.0.0.tgz#f04d0afd05518becce879d04709c47ecd93fba54"
|
||||
integrity sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 2"
|
||||
d3-drag "2"
|
||||
d3-interpolate "1 - 2"
|
||||
d3-selection "2"
|
||||
d3-transition "2"
|
||||
|
||||
d3@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/d3/-/d3-6.1.1.tgz#c92f8dfb5d6889ee0d518ea1e0e6afb8642ef991"
|
||||
integrity sha512-bJYW9wlS2uvP2EoMkcPptrUzLMHQKCbiSW+/la8iGSLZgs4KbI/f3Fch4RtnUA9PA+/nPlwyFYzTwDjX80Of8w==
|
||||
dependencies:
|
||||
d3-array "2"
|
||||
d3-axis "2"
|
||||
d3-brush "2"
|
||||
d3-chord "2"
|
||||
d3-color "2"
|
||||
d3-contour "2"
|
||||
d3-delaunay "5"
|
||||
d3-dispatch "2"
|
||||
d3-drag "2"
|
||||
d3-dsv "2"
|
||||
d3-ease "2"
|
||||
d3-fetch "2"
|
||||
d3-force "2"
|
||||
d3-format "2"
|
||||
d3-geo "2"
|
||||
d3-hierarchy "2"
|
||||
d3-interpolate "2"
|
||||
d3-path "2"
|
||||
d3-polygon "2"
|
||||
d3-quadtree "2"
|
||||
d3-random "2"
|
||||
d3-scale "3"
|
||||
d3-scale-chromatic "2"
|
||||
d3-selection "2"
|
||||
d3-shape "2"
|
||||
d3-time "2"
|
||||
d3-time-format "3"
|
||||
d3-timer "2"
|
||||
d3-transition "2"
|
||||
d3-zoom "2"
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||
@ -2673,6 +2913,11 @@ defined@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
|
||||
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
|
||||
|
||||
delaunator@4:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957"
|
||||
integrity sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
@ -3870,7 +4115,7 @@ human-signals@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
||||
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
iconv-lite@0.4, iconv-lite@0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
@ -6296,6 +6541,11 @@ regexp.prototype.flags@^1.2.0:
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.17.0-next.1"
|
||||
|
||||
regexparam@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f"
|
||||
integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g==
|
||||
|
||||
regexpp@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
|
||||
@ -6502,6 +6752,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
||||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
rw@1:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
|
||||
integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
@ -7080,6 +7335,13 @@ supports-color@^7.1.0:
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
svelte-spa-router@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte-spa-router/-/svelte-spa-router-2.2.0.tgz#ff56ca24890fccb0e6e58e729485a39ae92181ac"
|
||||
integrity sha512-S3KpmiBszjdjkU1QA9FyNgrSdQORbXWp4wl8R147Gl7KoNof/0SfFJ23+9E47jLiNz+lCw1uaCfeHJi2I60bVg==
|
||||
dependencies:
|
||||
regexparam "1.3.0"
|
||||
|
||||
svelte@^3.25.1:
|
||||
version "3.25.1"
|
||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.25.1.tgz#218def1243fea5a97af6eb60f5e232315bb57ac4"
|
||||
|
Loading…
Reference in New Issue
Block a user