mirror of
https://github.com/cupcakearmy/ora.git
synced 2024-12-21 23:56:31 +00:00
a lot of stuff
This commit is contained in:
parent
789dc37452
commit
22b2225a41
@ -1,2 +0,0 @@
|
||||
document.body.style.border = '5px solid blue'
|
||||
console.log('test')
|
3
icons/stopwatch-inv.svg
Normal file
3
icons/stopwatch-inv.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.0 KiB |
3
icons/stopwatch.svg
Normal file
3
icons/stopwatch.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.0 KiB |
BIN
icons/timer-dark.png
Normal file
BIN
icons/timer-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -8,14 +8,31 @@
|
||||
"icons": {
|
||||
"512": "icons/timer.png"
|
||||
},
|
||||
"browser_action": {
|
||||
"default_icon": {
|
||||
"512": "icons/stopwatch-inv.svg"
|
||||
},
|
||||
"default_title": "Ora Dash",
|
||||
"theme_icons": [
|
||||
{
|
||||
"light": "./icons/stopwatch-inv.svg",
|
||||
"dark": "./icons/stopwatch.svg",
|
||||
"size": 512
|
||||
}
|
||||
]
|
||||
},
|
||||
"permissions": ["<all_urls>", "tabs", "unlimitedStorage", "storage"],
|
||||
"background": {
|
||||
"scripts": ["./src/background/index.js"]
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "./src/options/index.html"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["borderify.js"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"web_accessible_resources": ["./icons/stopwatch.svg", "./icons/stopwatch-inv.svg"]
|
||||
}
|
||||
|
16
package.json
16
package.json
@ -1,19 +1,35 @@
|
||||
{
|
||||
"name": "ora",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "parcel --no-hmr manifest.json src/dashboard/index.html",
|
||||
"launch": "web-ext run -s dist"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 chrome versions",
|
||||
"last 2 firefox versions"
|
||||
],
|
||||
"dependencies": {
|
||||
"@amcharts/amcharts4": "^4.10.2",
|
||||
"chart.js": "^2.9.3",
|
||||
"dayjs": "^1.8.36",
|
||||
"faker": "^5.1.0",
|
||||
"google-palette": "^1.1.0",
|
||||
"lodash": "^4.17.20",
|
||||
"nedb": "^1.8.0",
|
||||
"nedb-promises": "^4.0.4",
|
||||
"spectre.css": "^0.5.9",
|
||||
"tailwindcss": "^1.8.10",
|
||||
"uhrwerk": "^1.0.2",
|
||||
"webextension-polyfill": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/firefox-webext-browser": "^78.0.1",
|
||||
"@types/lodash": "^4.14.161",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"parcel-plugin-svelte": "^4.0.6",
|
||||
"parcel-plugin-web-extension": "^1.6.1",
|
||||
"svelte": "^3.25.1",
|
||||
"web-ext": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,37 @@
|
||||
import browser from 'webextension-polyfill'
|
||||
// import Dexie from 'dexie'
|
||||
|
||||
// const db = new Dexie('myDb')
|
||||
// db.version(1).stores({
|
||||
// friends: `name, age`,
|
||||
// })
|
||||
import { dashboard } from '../shared/utils'
|
||||
import { Logs } from '../shared/db'
|
||||
|
||||
// import NeDB from 'nedb'
|
||||
browser.browserAction.onClicked.addListener(() => browser.tabs.create({ url: dashboard, active: true }))
|
||||
|
||||
// const db = new NeDB({filename: 'data.db'})
|
||||
// db.insert({planet: 'Earth'})
|
||||
import NeDB from 'nedb-promises'
|
||||
|
||||
const db = NeDB.create({
|
||||
filename: 'data.db',
|
||||
autoload: true,
|
||||
})
|
||||
|
||||
async function main() {
|
||||
await db.insert({ planet: 'Earth' })
|
||||
console.log('Docs', await db.find())
|
||||
}
|
||||
// main()
|
||||
const frequency = 3000
|
||||
|
||||
async function getAllTabs() {
|
||||
console.log('Getting all tabs')
|
||||
const all = await browser.tabs.query({})
|
||||
for (const tab of all) {
|
||||
console.log(tab.title, tab.id, tab.active, tab.highlighted)
|
||||
}
|
||||
const tabs = await browser.tabs.query({})
|
||||
const windows = await browser.windows.getAll()
|
||||
const active = tabs
|
||||
.filter((tab) => {
|
||||
const window = windows.find((window) => window.id === tab.windowId)
|
||||
return tab.active && window.focused
|
||||
})
|
||||
.map(({ id, title, url }) => {
|
||||
const { host } = new URL(url)
|
||||
return { id, title, host }
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
active.map(({ host }) => {
|
||||
if (host)
|
||||
return Logs.insert({
|
||||
timestamp: new Date(),
|
||||
host,
|
||||
frequency,
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
getAllTabs()
|
||||
}, 10000)
|
||||
|
||||
// document.addEventListener('DOMContentLoaded', () => {
|
||||
// console.log('Hello from BG')
|
||||
// })
|
||||
}, frequency)
|
||||
|
94
src/dashboard/App.svelte
Normal file
94
src/dashboard/App.svelte
Normal file
@ -0,0 +1,94 @@
|
||||
<script>
|
||||
import DateInput from './components/Date.svelte'
|
||||
import Chart from './components/Chart.svelte'
|
||||
import Dev from './components/Dev.svelte'
|
||||
|
||||
import { onMount } from 'svelte'
|
||||
import dayjs from 'dayjs'
|
||||
import { Duration } from 'uhrwerk'
|
||||
|
||||
import { data, countInGroup } from './lib'
|
||||
import { env } from 'process'
|
||||
|
||||
let counted
|
||||
let top = 20
|
||||
let start = dayjs().subtract(3, 'days').toDate()
|
||||
let end = new Date()
|
||||
let topData = {
|
||||
labels: [],
|
||||
data: [],
|
||||
}
|
||||
|
||||
async function calculate() {
|
||||
console.log('Calculating')
|
||||
const logs = await data({
|
||||
start,
|
||||
end: dayjs(end).endOf('day'),
|
||||
})
|
||||
counted = countInGroup(logs)
|
||||
// const onlyTop = counted.slice(0, top)
|
||||
// console.log(onlyTop)
|
||||
// topData = {
|
||||
// labels: onlyTop.map((n) => n[0]),
|
||||
// data: onlyTop.map((n) => n[1] / 1000),
|
||||
// }
|
||||
}
|
||||
|
||||
$: {
|
||||
start, end
|
||||
calculate()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
calculate()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.top {
|
||||
padding: 1em;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
max-width: 50em;
|
||||
}
|
||||
|
||||
.date > .spacer {
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.link {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0.25em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Dev />
|
||||
<div class="top rounded">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="text-4xl">Top {top}</h2>
|
||||
<div class="date flex px-1 rounded">
|
||||
<DateInput bind:date={start} />
|
||||
<div class="spacer" />
|
||||
<DateInput bind:date={end} />
|
||||
</div>
|
||||
</div>
|
||||
{#if counted}
|
||||
<Chart data={counted.slice(0, top)} />
|
||||
<table>
|
||||
<tr>
|
||||
<th>Time Spent</th>
|
||||
<th>Host</th>
|
||||
</tr>
|
||||
{#each counted as { host, total, human }}
|
||||
<tr>
|
||||
<td><b>{human}</b></td>
|
||||
<td class="link"><a href={'https://' + host}>{host}</a></td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
{/if}
|
||||
</div>
|
115
src/dashboard/components/Chart.svelte
Normal file
115
src/dashboard/components/Chart.svelte
Normal file
@ -0,0 +1,115 @@
|
||||
<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>
|
68
src/dashboard/components/ChartOld.svelte
Normal file
68
src/dashboard/components/ChartOld.svelte
Normal file
@ -0,0 +1,68 @@
|
||||
<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} />
|
12
src/dashboard/components/Date.svelte
Normal file
12
src/dashboard/components/Date.svelte
Normal file
@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export let name = ''
|
||||
export let date = new Date()
|
||||
let internal = dayjs(date).format('YYYY-MM-DD')
|
||||
|
||||
$: date = dayjs(internal, 'YYYY-MM-DD')
|
||||
</script>
|
||||
|
||||
<label class="form-label">{name}<input class="form-input" type="date" bind:value={internal} /> </label>
|
47
src/dashboard/components/Dev.svelte
Normal file
47
src/dashboard/components/Dev.svelte
Normal file
@ -0,0 +1,47 @@
|
||||
<script>
|
||||
import faker from 'faker'
|
||||
import day from 'dayjs'
|
||||
import { range, random } from 'lodash'
|
||||
|
||||
import { Logs } from '../../shared/db'
|
||||
|
||||
let loading = false
|
||||
|
||||
async function fill() {
|
||||
try {
|
||||
loading = true
|
||||
|
||||
const start = day().subtract('7', 'days').valueOf()
|
||||
const end = Date.now()
|
||||
|
||||
const all = []
|
||||
for (const n of range(50)) {
|
||||
const host = faker.internet.domainName()
|
||||
for (const m of range(random(500))) {
|
||||
const frequency = random(1, 10) * 1000
|
||||
const timestamp = new Date(random(start, end))
|
||||
all.push({ host, timestamp, frequency })
|
||||
}
|
||||
}
|
||||
console.log(`Generated ${all.length} data points`)
|
||||
console.debug(all)
|
||||
await Logs.insert(all)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function clear() {
|
||||
try {
|
||||
loading = true
|
||||
await Logs.remove({}, { multi: true })
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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>
|
||||
</div>
|
18
src/dashboard/index.html
Normal file
18
src/dashboard/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link href="../../node_modules/spectre.css/dist/spectre.min.css" rel="stylesheet" />
|
||||
<link href="../../node_modules/tailwindcss/dist/tailwind.css" rel="stylesheet" />
|
||||
<style>
|
||||
#root {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
30
src/dashboard/lib.js
Normal file
30
src/dashboard/lib.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { groupBy, orderBy } from 'lodash'
|
||||
import { Duration } from 'uhrwerk'
|
||||
|
||||
import { Logs } from '../shared/db'
|
||||
|
||||
export async function data({ start, end }) {
|
||||
const logs = await getLogsBetweenDates({ start, end })
|
||||
console.log('Found', logs.length)
|
||||
return groupBy(logs, 'host')
|
||||
}
|
||||
|
||||
export async function getLogsBetweenDates({ start, end }) {
|
||||
return await Logs.find({
|
||||
$and: [{ timestamp: { $gt: start } }, { timestamp: { $lt: end } }],
|
||||
})
|
||||
}
|
||||
|
||||
export function countInGroup(grouped) {
|
||||
const counted = Object.entries(grouped).map(([key, data]) => {
|
||||
const total = data.reduce((acc, cur) => acc + cur.frequency, 0)
|
||||
const human = new Duration(total, 'ms').humanize()
|
||||
// const human = 'some'
|
||||
return {
|
||||
host: key,
|
||||
total,
|
||||
human,
|
||||
}
|
||||
})
|
||||
return orderBy(counted, 'total', 'desc')
|
||||
}
|
6
src/dashboard/main.js
Normal file
6
src/dashboard/main.js
Normal file
@ -0,0 +1,6 @@
|
||||
// import browser from 'webextension-polyfill'
|
||||
// import '@babel/polyfill'
|
||||
|
||||
import App from './App.svelte'
|
||||
|
||||
new App({ target: window.document.getElementById('root') })
|
38
src/options/index.html
Normal file
38
src/options/index.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<link href="../../node_modules/spectre.css/dist/spectre.min.css" rel="stylesheet" />
|
||||
<link href="./main.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Ora</h1>
|
||||
<p>Ora helps you track down time consuming websites</p>
|
||||
<a href="../dashboard/index.html" target="_blank"><button class="btn btn-primary btn-lg">Go to the Dashboard</button></a>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<form id="form">
|
||||
<h4>Settings</h4>
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
Frequency <small>(Minutes)</small>
|
||||
<input id="frequency" class="form-input" type="number" min="3" step="1" />
|
||||
</label>
|
||||
<label class="form-label">
|
||||
Retention <small>(Days)</small>
|
||||
<input id="retention" class="form-input" type="number" min="3" max="365" step="1" />
|
||||
</label>
|
||||
<button id="reset" type="submit" class="btn">Reset</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
<div id="root"></div>
|
||||
<script src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
7
src/options/main.css
Normal file
7
src/options/main.css
Normal file
@ -0,0 +1,7 @@
|
||||
main {
|
||||
padding: 2em 1em;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 25em;
|
||||
}
|
42
src/options/main.js
Normal file
42
src/options/main.js
Normal file
@ -0,0 +1,42 @@
|
||||
import browser from 'webextension-polyfill'
|
||||
|
||||
const DEFAULT = {
|
||||
frequency: 3,
|
||||
retention: 90,
|
||||
}
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
return await browser.storage.local.get()
|
||||
} catch {
|
||||
return DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
async function save(settings) {
|
||||
return browser.storage.local.set(settings)
|
||||
}
|
||||
|
||||
function init() {
|
||||
const frequency = window.document.getElementById('frequency')
|
||||
const retention = window.document.getElementById('retention')
|
||||
const reset = window.document.getElementById('reset')
|
||||
const form = window.document.getElementById('form')
|
||||
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault()
|
||||
save({ frequency: frequency.value, retention: retention.value })
|
||||
})
|
||||
|
||||
reset.addEventListener('click', async () => {
|
||||
await browser.storage.local.clear()
|
||||
window.location.reload()
|
||||
})
|
||||
|
||||
load().then((saved) => {
|
||||
frequency.value = saved.frequency
|
||||
retention.value = saved.retention
|
||||
})
|
||||
}
|
||||
|
||||
window.document.addEventListener('DOMContentLoaded', init)
|
6
src/shared/db.js
Normal file
6
src/shared/db.js
Normal file
@ -0,0 +1,6 @@
|
||||
import NeDB from 'nedb-promises'
|
||||
|
||||
export const Logs = NeDB.create({
|
||||
filename: 'logs.db',
|
||||
autoload: true,
|
||||
})
|
3
src/shared/utils.js
Normal file
3
src/shared/utils.js
Normal file
@ -0,0 +1,3 @@
|
||||
import browser from 'webextension-polyfill'
|
||||
|
||||
export const dashboard = browser.runtime.getURL('./src/dashboard/index.html')
|
Loading…
Reference in New Issue
Block a user