big commit without a message
This commit is contained in:
parent
051d5b3c52
commit
d96fb38aa1
|
@ -11,8 +11,12 @@
|
|||
"bcrypt": "^5.0.1",
|
||||
"compression": "^1.7.1",
|
||||
"cookie": "^0.4.0",
|
||||
"echarts": "^5.1.2",
|
||||
"hat": "^0.0.3",
|
||||
"json-bigint": "^1.0.0",
|
||||
"nbt-ts": "^1.3.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-gzip": "^1.1.2",
|
||||
"polka": "^0.5.2",
|
||||
"sirv": "^1.0.0"
|
||||
},
|
||||
|
@ -34,7 +38,9 @@
|
|||
"@types/compression": "^1.7.0",
|
||||
"@types/cookie": "^0.4.0",
|
||||
"@types/hat": "^0.0.1",
|
||||
"@types/json-bigint": "^1.0.1",
|
||||
"@types/node": "^14.11.1",
|
||||
"@types/node-gzip": "^1.1.0",
|
||||
"@types/polka": "^0.5.1",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
|
@ -1864,6 +1870,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/json-bigint": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.1.tgz",
|
||||
"integrity": "sha512-zpchZLNsNuzJHi6v64UBoFWAvQlPhch7XAi36FkH6tL1bbbmimIF+cS7vwkzY4u5RaSWMoflQfu+TshMPPw8uw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
|
@ -1875,6 +1887,15 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz",
|
||||
"integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A=="
|
||||
},
|
||||
"node_modules/@types/node-gzip": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-gzip/-/node-gzip-1.1.0.tgz",
|
||||
"integrity": "sha512-j7cGb6HIOZbDx3sqe9/9VAPeSvyt143yu5k35gzRXE3mxEgK6BOZ6BAiJ3ToXBcJqLzL9Cr53dav21jlp3f9gw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/polka": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/polka/-/polka-0.5.2.tgz",
|
||||
|
@ -2095,6 +2116,14 @@
|
|||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz",
|
||||
"integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
|
@ -2490,6 +2519,20 @@
|
|||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.1.2.tgz",
|
||||
"integrity": "sha512-okUhO4sw22vwZp+rTPNjd/bvTdpug4K4sHNHyrV8NdAncIX9/AarlolFqtJCAYKGFYhUBNjIWu1EznFrSWTFxg==",
|
||||
"dependencies": {
|
||||
"tslib": "2.0.3",
|
||||
"zrender": "5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts/node_modules/tslib": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.3.768",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.768.tgz",
|
||||
|
@ -2947,6 +2990,14 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/json-bigint": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
|
||||
"dependencies": {
|
||||
"bignumber.js": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
|
||||
|
@ -3169,6 +3220,11 @@
|
|||
"node": ">=8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nbt-ts": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/nbt-ts/-/nbt-ts-1.3.3.tgz",
|
||||
"integrity": "sha512-xmEVWDJzO7YdA2YJHqAkfiOKlKECXe/hfNB10t3W7aDJsCXTjXyRbhP5HYvCwrMefGk8p6arQqeMO2V6djyfxQ=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
|
@ -3199,6 +3255,11 @@
|
|||
"node": "4.x || >=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gzip": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-gzip/-/node-gzip-1.1.2.tgz",
|
||||
"integrity": "sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw=="
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "1.1.73",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz",
|
||||
|
@ -4196,6 +4257,19 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.1.1.tgz",
|
||||
"integrity": "sha512-oeWlmUZPQdS9f5hK4pV21tHPqA3wgQ7CkKkw7l0CCBgWlJ/FP+lRgLFtUBW6yam4JX8y9CdHJo1o587VVrbcoQ==",
|
||||
"dependencies": {
|
||||
"tslib": "2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender/node_modules/tslib": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -5481,6 +5555,12 @@
|
|||
"integrity": "sha512-GQoFDN07Knft7pvik72m/ddy8wmwMykzJEPZLoT7Wp3PHZsttdAbQ51qKf1DLWAdU6Ac31xon1Ji3g0hqQv6sw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/json-bigint": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.1.tgz",
|
||||
"integrity": "sha512-zpchZLNsNuzJHi6v64UBoFWAvQlPhch7XAi36FkH6tL1bbbmimIF+cS7vwkzY4u5RaSWMoflQfu+TshMPPw8uw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
|
@ -5492,6 +5572,15 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.4.tgz",
|
||||
"integrity": "sha512-8kQ3+wKGRNN0ghtEn7EGps/B8CzuBz1nXZEIGGLP2GnwbqYn4dbTs7k+VKLTq1HvZLRCIDtN3Snx1Ege8B7L5A=="
|
||||
},
|
||||
"@types/node-gzip": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-gzip/-/node-gzip-1.1.0.tgz",
|
||||
"integrity": "sha512-j7cGb6HIOZbDx3sqe9/9VAPeSvyt143yu5k35gzRXE3mxEgK6BOZ6BAiJ3ToXBcJqLzL9Cr53dav21jlp3f9gw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/polka": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/polka/-/polka-0.5.2.tgz",
|
||||
|
@ -5681,6 +5770,11 @@
|
|||
"node-addon-api": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz",
|
||||
"integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
|
@ -5988,6 +6082,22 @@
|
|||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"echarts": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.1.2.tgz",
|
||||
"integrity": "sha512-okUhO4sw22vwZp+rTPNjd/bvTdpug4K4sHNHyrV8NdAncIX9/AarlolFqtJCAYKGFYhUBNjIWu1EznFrSWTFxg==",
|
||||
"requires": {
|
||||
"tslib": "2.0.3",
|
||||
"zrender": "5.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.768",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.768.tgz",
|
||||
|
@ -6335,6 +6445,14 @@
|
|||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"dev": true
|
||||
},
|
||||
"json-bigint": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
|
||||
"requires": {
|
||||
"bignumber.js": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
|
||||
|
@ -6497,6 +6615,11 @@
|
|||
"resolved": "https://registry.npmjs.org/multi-part-lite/-/multi-part-lite-1.0.0.tgz",
|
||||
"integrity": "sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw=="
|
||||
},
|
||||
"nbt-ts": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/nbt-ts/-/nbt-ts-1.3.3.tgz",
|
||||
"integrity": "sha512-xmEVWDJzO7YdA2YJHqAkfiOKlKECXe/hfNB10t3W7aDJsCXTjXyRbhP5HYvCwrMefGk8p6arQqeMO2V6djyfxQ=="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
|
@ -6521,6 +6644,11 @@
|
|||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||
},
|
||||
"node-gzip": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-gzip/-/node-gzip-1.1.2.tgz",
|
||||
"integrity": "sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw=="
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.73",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz",
|
||||
|
@ -7259,6 +7387,21 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"zrender": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.1.1.tgz",
|
||||
"integrity": "sha512-oeWlmUZPQdS9f5hK4pV21tHPqA3wgQ7CkKkw7l0CCBgWlJ/FP+lRgLFtUBW6yam4JX8y9CdHJo1o587VVrbcoQ==",
|
||||
"requires": {
|
||||
"tslib": "2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,12 @@
|
|||
"bcrypt": "^5.0.1",
|
||||
"compression": "^1.7.1",
|
||||
"cookie": "^0.4.0",
|
||||
"echarts": "^5.1.2",
|
||||
"hat": "^0.0.3",
|
||||
"json-bigint": "^1.0.0",
|
||||
"nbt-ts": "^1.3.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-gzip": "^1.1.2",
|
||||
"polka": "^0.5.2",
|
||||
"sirv": "^1.0.0"
|
||||
},
|
||||
|
@ -37,7 +41,9 @@
|
|||
"@types/compression": "^1.7.0",
|
||||
"@types/cookie": "^0.4.0",
|
||||
"@types/hat": "^0.0.1",
|
||||
"@types/json-bigint": "^1.0.1",
|
||||
"@types/node": "^14.11.1",
|
||||
"@types/node-gzip": "^1.1.0",
|
||||
"@types/polka": "^0.5.1",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
export let hrefPrefix = "";
|
||||
import { login } from "./stores";
|
||||
import { getGraphs } from "../lib/api";
|
||||
|
||||
|
@ -15,14 +16,10 @@
|
|||
</span>
|
||||
{:then graphs}
|
||||
{#each graphs as graph}
|
||||
<a class="item" href="./graph/{graph.graph}">
|
||||
<a class="item" href="{hrefPrefix}/graph/{graph.graph}">
|
||||
<h2>{graph.data.name} <span>#{graph.graph}</span></h2>
|
||||
<h3>
|
||||
Value:
|
||||
{#each graph.data.value as key}
|
||||
<span>{key}</span>
|
||||
{/each}
|
||||
</h3>
|
||||
<p>{graph.data.style}</p>
|
||||
<code>{graph.data.value}</code>
|
||||
</a>
|
||||
{/each}
|
||||
<!-- svelte-ignore a11y-missing-content -->
|
||||
|
@ -36,6 +33,7 @@
|
|||
|
||||
<style lang="scss">
|
||||
@use "../styles/GridList";
|
||||
@use "../styles/GridItems";
|
||||
a.item {
|
||||
text-decoration: none;
|
||||
color: hsl(30, 20%, 90%);
|
||||
|
@ -51,16 +49,7 @@
|
|||
font-weight: 100;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
h3 span {
|
||||
font-weight: 400;
|
||||
}
|
||||
h3 span::after {
|
||||
content: " > ";
|
||||
font-size: 0.6em;
|
||||
color: hsl(30, 5%, 60%);
|
||||
vertical-align: middle;
|
||||
}
|
||||
h3 span:nth-last-child(1)::after {
|
||||
content: "";
|
||||
.add {
|
||||
@include GridItems.add;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,21 +1,48 @@
|
|||
<script lang="ts">
|
||||
export let segment: string | undefined;
|
||||
import { postLogout } from "../lib/api";
|
||||
import { getPermissions, postLogout } from "../lib/api";
|
||||
import { checkPermissions, Permissions } from "../lib/permissions";
|
||||
import { extraNav, login } from "./stores";
|
||||
|
||||
let profiles = false;
|
||||
let users = false;
|
||||
$: if ($login !== undefined) {
|
||||
getPermissions($login?.token).then(permissions => {
|
||||
if (checkPermissions([Permissions.VIEW_PROFILES], permissions)) {
|
||||
profiles = true;
|
||||
}
|
||||
if (checkPermissions([Permissions.VIEW_USERS], permissions)) {
|
||||
users = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav>
|
||||
<a href="/" class:active={segment === undefined || segment === "profile"}>
|
||||
<a
|
||||
href="/profiles"
|
||||
class:active={segment === "profiles" || segment === "profile"}
|
||||
class:invisible={!profiles}
|
||||
>
|
||||
<h3>Profiles</h3>
|
||||
</a>
|
||||
<span class="sep" />
|
||||
<a
|
||||
href="/graphs"
|
||||
class:active={segment === "graphs" || segment === "graph"}
|
||||
class:invisible={!profiles}
|
||||
>
|
||||
<h3>Graphs</h3>
|
||||
</a>
|
||||
<span class="sep" />
|
||||
<a
|
||||
href="/users"
|
||||
class:active={segment === "users" || segment === "user"}
|
||||
class:invisible={!users}
|
||||
>
|
||||
<h3>Users</h3>
|
||||
</a>
|
||||
<span class="sep" />
|
||||
{#if $extraNav !== undefined}
|
||||
<span class="extra">
|
||||
<h3>
|
||||
|
@ -128,4 +155,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.invisible,
|
||||
.invisible + .sep {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -49,3 +49,28 @@ function createExtraNav() {
|
|||
}
|
||||
|
||||
export const extraNav = createExtraNav();
|
||||
|
||||
function createCache() {
|
||||
const { subscribe, set, update } = writable<{
|
||||
[key: string]: string;
|
||||
}>({});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set: (key: string, value: string) =>
|
||||
update(current => ({
|
||||
...current,
|
||||
[key]: value
|
||||
})),
|
||||
unset: (key: string) =>
|
||||
update(current => {
|
||||
delete current[key];
|
||||
return current;
|
||||
}),
|
||||
clear: () => set({}),
|
||||
update
|
||||
};
|
||||
}
|
||||
|
||||
export const playerCache = createCache();
|
||||
export const profileCache = createCache();
|
||||
|
|
|
@ -55,11 +55,33 @@ export async function getProfiles(
|
|||
|
||||
/**
|
||||
* Get a list of skyblock profiles by player
|
||||
* @param token Authorization token
|
||||
* @param player Player's UUID
|
||||
* @returns A list of profiles
|
||||
*/
|
||||
export async function getPlayerProfiles(player: string): Promise<Profile[]> {
|
||||
let response = await request(`/api/profiles/${player}`);
|
||||
export async function getPlayerProfiles(
|
||||
token: string,
|
||||
player: string
|
||||
): Promise<Profile[]> {
|
||||
let response = await request(`/api/profiles/${player}`, {
|
||||
headers: { Authorization: token }
|
||||
});
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a palayer's profile cute name
|
||||
* @param player Player's UUID
|
||||
* @param profile Profile's UUID
|
||||
* @returns Profile's cute name
|
||||
*/
|
||||
export async function getProfileCuteName(
|
||||
player: string,
|
||||
profile: string
|
||||
): Promise<string> {
|
||||
let response = await request(
|
||||
`/api/profiles/${player}/${profile}/cute-name`
|
||||
);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
|
@ -119,3 +141,57 @@ export async function putProfiles(
|
|||
);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get permissions of authenticated user
|
||||
* @param token Authorization token
|
||||
* @returns Array of permissions
|
||||
*/
|
||||
export async function getPermissions(token?: string): Promise<string[]> {
|
||||
let response = await request(
|
||||
"/api/permissions",
|
||||
token ? { headers: { Authorization: token } } : {}
|
||||
);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function putGraph(
|
||||
token: string | undefined,
|
||||
type: string,
|
||||
name: string,
|
||||
style: string,
|
||||
value: string,
|
||||
xAxisName: string = "Date & time",
|
||||
xAxisType: string = "time",
|
||||
yAxisName: string = "",
|
||||
yAxisType: string = "value"
|
||||
): Promise<void> {
|
||||
let searchParams = new URLSearchParams({
|
||||
type,
|
||||
name,
|
||||
style,
|
||||
value,
|
||||
x_axis_name: xAxisName,
|
||||
x_axis_type: xAxisType,
|
||||
y_axis_name: yAxisName,
|
||||
y_axis_type: yAxisType
|
||||
});
|
||||
await request("/api/graphs?" + searchParams.toString(), {
|
||||
method: "PUT",
|
||||
...(token ? { headers: { Authorization: token } } : {})
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
export async function getGraph(
|
||||
token: string | undefined,
|
||||
graph: string,
|
||||
profile: string,
|
||||
member: string
|
||||
): Promise<[number, number][]> {
|
||||
let response = await request(
|
||||
`/api/graphs/${graph}/${profile}/${member}`,
|
||||
token ? { headers: { Authorization: token } } : {}
|
||||
);
|
||||
return response.json();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import type { Document } from "arangojs/documents";
|
|||
import { compare } from "bcrypt";
|
||||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import type { Session, User } from "../routes/api/_types";
|
||||
import type { Profile } from "./hypixel";
|
||||
import { checkPermissions } from "./permissions";
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,7 +6,38 @@ export type HypixelResponse<T> = {
|
|||
} & T;
|
||||
|
||||
export const ENDPOINT = "https://api.hypixel.net/";
|
||||
const request = requestWithDefaults(ENDPOINT);
|
||||
|
||||
// Request function with rate limit handling
|
||||
const request: ReturnType<typeof requestWithDefaults> = (() => {
|
||||
async function req(
|
||||
endpoint: string,
|
||||
init?: RequestInit
|
||||
): Promise<Response> {
|
||||
try {
|
||||
let response = await requestWithDefaults(ENDPOINT)(endpoint, init);
|
||||
console.log(
|
||||
`Left: ${response.headers.get(
|
||||
"ratelimit-remaining"
|
||||
)}\tReset: ${response.headers.get("ratelimit-reset")}s`
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
let response: Response = e.response;
|
||||
if (response.status === 429) {
|
||||
const reset = response.headers.get("ratelimit-reset");
|
||||
if (reset) {
|
||||
console.log("Rate limited, waiting for", reset, "seconds");
|
||||
await new Promise(resolve =>
|
||||
setTimeout(resolve, (2 + parseInt(reset, 10)) * 1000)
|
||||
);
|
||||
return req(endpoint, init);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return req;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Get Hypixel API key information
|
||||
|
@ -38,6 +69,7 @@ export type Profile = {
|
|||
cute_name: string;
|
||||
banking: any | null;
|
||||
game_mode: string | null;
|
||||
timestamp: number;
|
||||
};
|
||||
/**
|
||||
* Get Skyblock profiles by player
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
export enum Permissions {
|
||||
ALL = "*",
|
||||
VIEW = "view",
|
||||
|
||||
VIEW_PROFILES = "view_profiles",
|
||||
ADD_PROFILE = "add_profile",
|
||||
REMOVE_PROFILE = "remove_profile",
|
||||
|
||||
ADD_GRAPH = "add_graph",
|
||||
MANUAL_GRAPH = "manual_graph", // separate because allows for remote code execution
|
||||
REMOVE_GRAPH = "remove_graph",
|
||||
|
||||
VIEW_USERS = "view_users",
|
||||
|
|
|
@ -1,16 +1,59 @@
|
|||
import { getOnlineStatus, getProfileByUUID } from "./lib/hypixel";
|
||||
import nbt from "nbt-ts";
|
||||
import gzip from "node-gzip";
|
||||
import { getOnlineStatus, getProfileByUUID, Profile } from "./lib/hypixel";
|
||||
|
||||
export async function log(key: string, profile: string) {
|
||||
let r = await getProfileByUUID(key, profile);
|
||||
if (!r.success || !r.profile) throw new Error("No success from Hypixel");
|
||||
|
||||
let d = r.profile;
|
||||
let d: Profile = r.profile as any;
|
||||
d.timestamp = Date.now();
|
||||
|
||||
for (const member in d.members) {
|
||||
let onlineStatus = await getOnlineStatus(key, member);
|
||||
if (onlineStatus.success && onlineStatus.session) {
|
||||
d.members[member].online_status = onlineStatus.session;
|
||||
d.members[member].online_status.timestamp = Date.now();
|
||||
}
|
||||
|
||||
const inventories = [
|
||||
"inv_armor",
|
||||
"quiver",
|
||||
"talisman_bag",
|
||||
"backpack_icons",
|
||||
"fishing_bag",
|
||||
"ender_chest_contents",
|
||||
"wardrobe_contents",
|
||||
"potion_bag",
|
||||
"personal_vault_contents",
|
||||
"inv_contents",
|
||||
"candy_inventory_contents"
|
||||
];
|
||||
if (d.members[member].backpack_contents) {
|
||||
for (const backpack in d.members[member].backpack_contents) {
|
||||
await decodeInventory(
|
||||
d.members[member].backpack_contents[backpack]
|
||||
);
|
||||
}
|
||||
}
|
||||
if (d.members[member].backpack_icons) {
|
||||
for (const icon in d.members[member].backpack_icons) {
|
||||
await decodeInventory(d.members[member].backpack_icons[icon]);
|
||||
}
|
||||
}
|
||||
for (const idx in inventories) {
|
||||
const inv = inventories[idx];
|
||||
await decodeInventory(d.members[member][inv]);
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
async function decodeInventory(inventory: any) {
|
||||
if (inventory?.data) {
|
||||
inventory.data = nbt.decode(
|
||||
await gzip.ungzip(Buffer.from(inventory.data, "base64"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,9 @@
|
|||
padding-top: $navbar-padding;
|
||||
min-height: calc(100% - #{$navbar-padding} - 0.75em);
|
||||
}
|
||||
:global(*) {
|
||||
:global(button),
|
||||
:global(input),
|
||||
:global(textarea) {
|
||||
font-family: "Fira Code", monospace;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -25,10 +25,26 @@ export type Tracker = Config<
|
|||
}
|
||||
>;
|
||||
|
||||
export type Graph = Config<
|
||||
"graph",
|
||||
{
|
||||
// Graphs
|
||||
export type Axis = {
|
||||
name?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
export type BaseGraph<TS = string, T = any> = Config<
|
||||
TS,
|
||||
T & {
|
||||
name: string;
|
||||
value: (string | number)[];
|
||||
style: string;
|
||||
x_axis?: Axis;
|
||||
y_axis?: Axis;
|
||||
}
|
||||
>;
|
||||
|
||||
export type Graph = BaseGraph<
|
||||
"manual_graph",
|
||||
{
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
>;
|
||||
|
|
|
@ -2,18 +2,20 @@ import { aql } from "arangojs";
|
|||
import type { Document } from "arangojs/documents";
|
||||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { authorizeRequest } from "../../lib/butil";
|
||||
import { Permissions } from "../../lib/permissions";
|
||||
import { checkPermissions, Permissions } from "../../lib/permissions";
|
||||
import { db } from "../../server";
|
||||
import type { Graph } from "./_types";
|
||||
|
||||
export async function get(req: IncomingMessage, res: ServerResponse) {
|
||||
const [_, exit] = await authorizeRequest(req, res, db, [Permissions.VIEW]);
|
||||
const [_, exit] = await authorizeRequest(req, res, db, [
|
||||
Permissions.VIEW_PROFILES
|
||||
]);
|
||||
if (exit) return;
|
||||
|
||||
let graphs: (Graph & { graph: string })[] = [];
|
||||
const cursor = await db.query(aql`
|
||||
FOR doc IN config
|
||||
FILTER doc.type == "graph"
|
||||
FILTER CONTAINS(doc.type, "graph")
|
||||
RETURN doc
|
||||
`);
|
||||
while (cursor.hasNext) {
|
||||
|
@ -27,3 +29,93 @@ export async function get(req: IncomingMessage, res: ServerResponse) {
|
|||
res.writeHead(200);
|
||||
res.end(JSON.stringify(graphs));
|
||||
}
|
||||
|
||||
export async function put(req: IncomingMessage, res: ServerResponse) {
|
||||
const [user, exit] = await authorizeRequest(req, res, db, [
|
||||
Permissions.ADD_GRAPH
|
||||
]);
|
||||
if (exit) return;
|
||||
|
||||
const url = new URL(req.url!, "https://example.com/");
|
||||
if (!url.searchParams.get("name")) {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "name query parameter should be set"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!url.searchParams.get("style")) {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "style query parameter should be set"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!url.searchParams.get("type")) {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "type query parameter should be set"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (url.searchParams.get("type")) {
|
||||
case "manual_graph":
|
||||
if (
|
||||
!checkPermissions([Permissions.MANUAL_GRAPH], user!.permissions)
|
||||
) {
|
||||
res.writeHead(403);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "You are unauthroized to make manual graphs"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!url.searchParams.get("value")) {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "value query parameter should be set"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
await db.collection("config").save({
|
||||
type: "manual_graph",
|
||||
data: {
|
||||
name: url.searchParams.get("name"),
|
||||
style: url.searchParams.get("style"),
|
||||
value: url.searchParams.get("value"),
|
||||
x_axis: {
|
||||
name:
|
||||
url.searchParams.get("x_axis_name") ||
|
||||
"Date & time",
|
||||
type: url.searchParams.get("x_axis_type") || "time"
|
||||
},
|
||||
y_axis: {
|
||||
name: url.searchParams.get("y_axis_name") || undefined,
|
||||
type: url.searchParams.get("y_axis_type") || "value"
|
||||
}
|
||||
}
|
||||
} as Graph);
|
||||
res.writeHead(204);
|
||||
res.end();
|
||||
break;
|
||||
default:
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "Invalid graph type"
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { authorizeRequest } from "../../../../../lib/butil";
|
||||
import { Permissions } from "../../../../../lib/permissions";
|
||||
import { db } from "../../../../../server";
|
||||
import type { Graph } from "../../../_types";
|
||||
|
||||
export async function get(
|
||||
req: IncomingMessage & {
|
||||
params: { graph: string; profile: string; player: string };
|
||||
},
|
||||
res: ServerResponse
|
||||
) {
|
||||
const [_, exit] = await authorizeRequest(req, res, db, [
|
||||
Permissions.VIEW_PROFILES
|
||||
]);
|
||||
if (exit) return;
|
||||
|
||||
const cnf = db.collection("config");
|
||||
const profile = db.collection("c" + req.params.profile);
|
||||
if (!(await profile.exists())) {
|
||||
res.writeHead(404);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "Profile not found"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let graph: Graph = await cnf.document(req.params.graph);
|
||||
switch (graph.type) {
|
||||
case "manual_graph":
|
||||
let data = await (
|
||||
await db.query({
|
||||
query: `FOR profile IN ${profile.name}
|
||||
LET member = profile.members[@member_uuid]
|
||||
${graph.data.value}`,
|
||||
bindVars: {
|
||||
member_uuid: req.params.player
|
||||
}
|
||||
})
|
||||
).all();
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify(data));
|
||||
return;
|
||||
default:
|
||||
res.writeHead(404);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "Not a graph ID"
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { authorizeRequest } from "../../lib/butil";
|
||||
import { db } from "../../server";
|
||||
|
||||
export async function get(req: IncomingMessage, res: ServerResponse) {
|
||||
const [user, exit] = await authorizeRequest(req, res, db, []);
|
||||
if (exit) return;
|
||||
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify(user!.permissions));
|
||||
}
|
|
@ -8,7 +8,9 @@ import { db, API_KEY } from "../../server";
|
|||
import type { Tracker } from "./_types";
|
||||
|
||||
export async function get(req: IncomingMessage, res: ServerResponse) {
|
||||
const [_, exit] = await authorizeRequest(req, res, db, [Permissions.VIEW]);
|
||||
const [_, exit] = await authorizeRequest(req, res, db, [
|
||||
Permissions.VIEW_PROFILES
|
||||
]);
|
||||
if (exit) return;
|
||||
|
||||
let profiles: (Tracker & { profile: string })[] = [];
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { authorizeRequest } from "../../../lib/butil";
|
||||
import { Permissions } from "../../../lib/permissions";
|
||||
import { getProfilesByPlayer } from "../../../lib/hypixel";
|
||||
import { API_KEY } from "../../../server";
|
||||
import { API_KEY, db } from "../../../server";
|
||||
|
||||
export async function get(
|
||||
req: IncomingMessage & { params: { player: string } },
|
||||
res: ServerResponse
|
||||
) {
|
||||
const [_, exit] = await authorizeRequest(req, res, db, [
|
||||
Permissions.ADD_PROFILE
|
||||
]);
|
||||
if (exit) return;
|
||||
|
||||
let response = await getProfilesByPlayer(API_KEY || "", req.params.player);
|
||||
if (response.success && response.profiles !== undefined) {
|
||||
res.writeHead(200);
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { getProfilesByPlayer } from "../../../../../lib/hypixel";
|
||||
import { API_KEY, profileNameCache } from "../../../../../server";
|
||||
|
||||
export async function get(
|
||||
req: IncomingMessage & { params: { player: string; profile: string } },
|
||||
res: ServerResponse
|
||||
) {
|
||||
if (profileNameCache[req.params.player]?.[req.params.profile]) {
|
||||
res.writeHead(200);
|
||||
res.end(
|
||||
JSON.stringify(
|
||||
profileNameCache[req.params.player]?.[req.params.profile]
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let response = await getProfilesByPlayer(API_KEY || "", req.params.player);
|
||||
if (response.success && response.profiles !== undefined) {
|
||||
if (profileNameCache[req.params.player] === undefined) {
|
||||
profileNameCache[req.params.player] = {};
|
||||
}
|
||||
response.profiles.forEach(
|
||||
p =>
|
||||
(profileNameCache[req.params.player][p.profile_id] =
|
||||
p.cute_name)
|
||||
);
|
||||
res.writeHead(200);
|
||||
res.end(
|
||||
response.profiles.find(p => p.profile_id === req.params.profile)
|
||||
?.cute_name
|
||||
);
|
||||
} else {
|
||||
res.writeHead(response._response.status);
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
message: "Unsuccessful Hypixel API response"
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<script lang="ts">
|
||||
import { goto } from "@sapper/app";
|
||||
import { login, extraNav } from "../../components/stores";
|
||||
import { putGraph } from "../../lib/api";
|
||||
|
||||
$extraNav; // important for subscription to store to be activated
|
||||
extraNav.set([["Add", () => (type = "")]]);
|
||||
|
||||
let type = "";
|
||||
let name = "";
|
||||
let style = "";
|
||||
let value = "";
|
||||
let status = "";
|
||||
|
||||
let xAxisName = "Date & time";
|
||||
let yAxisName = "";
|
||||
let xAxisType = "time";
|
||||
let yAxisType = "value";
|
||||
|
||||
$: if (type !== "") {
|
||||
extraNav.set([
|
||||
["Add", () => (type = "")],
|
||||
{
|
||||
manual_graph: "Manual Graph"
|
||||
}[type]!
|
||||
]);
|
||||
} else {
|
||||
extraNav.set([["Add", () => (type = "")]]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Add Graph - Skyblock Data Tracker</title>
|
||||
</svelte:head>
|
||||
|
||||
<div id="list">
|
||||
{#if type === ""}
|
||||
<button class="item" on:click={() => (type = "manual_graph")}>
|
||||
<h1>Manual Graph</h1>
|
||||
</button>
|
||||
{:else if type === "manual_graph"}
|
||||
<form
|
||||
class="item"
|
||||
on:submit={async event => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
await putGraph($login?.token, type, name, style, value);
|
||||
goto("/graphs");
|
||||
} catch (e) {
|
||||
if (
|
||||
e.response?.status === 401 ||
|
||||
e.response?.status === 403
|
||||
) {
|
||||
alert("Unauthorised");
|
||||
} else {
|
||||
alert("An error occured while adding the profile");
|
||||
}
|
||||
status = "Error";
|
||||
}
|
||||
}}
|
||||
>
|
||||
<h1>Manual Graph</h1>
|
||||
<p id="status">{status}</p>
|
||||
<input type="text" placeholder="Name" bind:value={name} />
|
||||
<input type="text" placeholder="Style" bind:value={style} />
|
||||
<textarea rows="16" placeholder="AQL" bind:value />
|
||||
<h2>X Axis</h2>
|
||||
<p>
|
||||
Name: <input type="text" bind:value={xAxisName} />
|
||||
</p>
|
||||
<p>
|
||||
Type: <input type="text" bind:value={xAxisType} />
|
||||
</p>
|
||||
<h2>Y Axis</h2>
|
||||
<p>
|
||||
Name: <input type="text" bind:value={yAxisName} />
|
||||
</p>
|
||||
<p>
|
||||
Type: <input type="text" bind:value={yAxisType} />
|
||||
</p>
|
||||
<input type="submit" value="Add" />
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../../styles/GridList";
|
||||
@use "../../styles/CodeForm";
|
||||
|
||||
.item {
|
||||
color: hsl(30, 20%, 90%);
|
||||
font-size: 1.17em;
|
||||
}
|
||||
button.item:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
|
@ -1,70 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { login } from "../components/stores";
|
||||
import { getProfiles, getUsername } from "../lib/api";
|
||||
import { goto } from "@sapper/app";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let profilesPromise: ReturnType<typeof getProfiles> = new Promise(() => {});
|
||||
$: if ($login !== undefined) {
|
||||
profilesPromise = getProfiles($login?.token);
|
||||
}
|
||||
onMount(() => goto("/profiles"));
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Skyblock Data Tracker</title>
|
||||
</svelte:head>
|
||||
|
||||
<div id="list">
|
||||
{#await profilesPromise}
|
||||
<div class="item">
|
||||
<h2>Fetching profiles...</h2>
|
||||
</div>
|
||||
{:then profiles}
|
||||
{#each profiles as profile}
|
||||
<button type="button" class="item">
|
||||
{#each profile.data.members as member, idx}
|
||||
{#await getUsername(member)}
|
||||
<a href="/profile/{profile.profile}/member/{member}"
|
||||
>...</a
|
||||
>
|
||||
{:then username}
|
||||
<a href="/profile/{profile.profile}/member/{member}">
|
||||
{username}
|
||||
{#if idx === 0 && profile.data.name}
|
||||
@ {profile.data.name}
|
||||
{/if}
|
||||
</a>
|
||||
{:catch error}
|
||||
<p style="color: red">{error.message}</p>
|
||||
{/await}
|
||||
{/each}
|
||||
</button>
|
||||
{/each}
|
||||
<!-- svelte-ignore a11y-missing-content -->
|
||||
<a class="item add" href="/profile/new" />
|
||||
{:catch error}
|
||||
<div class="item">
|
||||
<h2 style="color: red">{error.message}</h2>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../styles/GridList";
|
||||
.item {
|
||||
font-size: 1.17em;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
color: hsl(30, 20%, 90%);
|
||||
text-decoration: none;
|
||||
font-family: "Minecraft";
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
a:nth-child(1) {
|
||||
color: hsl(60, 100%, 50%);
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,4 +12,26 @@
|
|||
<script lang="ts">
|
||||
export let profile: string;
|
||||
export let member: string;
|
||||
import Graphs from "../../../../components/Graphs.svelte";
|
||||
import { goto } from "@sapper/app";
|
||||
import { extraNav, login } from "../../../../components/stores";
|
||||
import { getPlayerProfiles, getUsername } from "../../../../lib/api";
|
||||
|
||||
$extraNav;
|
||||
$: if ($login !== undefined) {
|
||||
(async () => {
|
||||
let username = await getUsername(member);
|
||||
let profiles = await getPlayerProfiles(member);
|
||||
extraNav.set([
|
||||
[
|
||||
username +
|
||||
" @ " +
|
||||
profiles.find(p => p.profile_id === profile)?.cute_name,
|
||||
() => goto(`/profile/${profile}/member/${member}`)
|
||||
]
|
||||
]);
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Graphs hrefPrefix="/profile/{profile}/member/{member}" />
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
<script context="module" lang="ts">
|
||||
import type common from "@sapper/common";
|
||||
|
||||
export async function preload(page: common.Page) {
|
||||
return {
|
||||
profile: page.params.profile,
|
||||
member: page.params.member,
|
||||
graph: page.params.graph
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let profile: string;
|
||||
export let member: string;
|
||||
export let graph: string;
|
||||
|
||||
import { onMount } from "svelte";
|
||||
import { init } from "echarts";
|
||||
import { getGraph, getGraphs } from "../../../../../../lib/api";
|
||||
import { login } from "../../../../../../components/stores";
|
||||
|
||||
onMount(async () => {
|
||||
let graphs = await getGraphs($login?.token);
|
||||
let g = graphs.find(g => g.graph === graph);
|
||||
|
||||
let data = await getGraph($login?.token, graph, profile, member);
|
||||
|
||||
let c = document.querySelector("div#list");
|
||||
if (c && g) {
|
||||
init(c as HTMLElement, "dark").setOption({
|
||||
title: {
|
||||
text: g.data.name
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis"
|
||||
},
|
||||
xAxis: g.data.x_axis || {
|
||||
name: "Date & time",
|
||||
type: "time"
|
||||
},
|
||||
yAxis: g.data.y_axis || {
|
||||
type: "value"
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: g.data.name,
|
||||
type: g.data.style,
|
||||
data: data
|
||||
}
|
||||
],
|
||||
dataZoom: [
|
||||
{
|
||||
id: "dataZoomX",
|
||||
type: "slider",
|
||||
filterMode: "filter"
|
||||
},
|
||||
{
|
||||
id: "dataZoomY",
|
||||
type: "slider",
|
||||
filterMode: "empty"
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="list">
|
||||
<div class="item"><h1>Loading...</h1></div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../../../../../../styles/GridList";
|
||||
</style>
|
|
@ -85,7 +85,7 @@
|
|||
);
|
||||
}
|
||||
}
|
||||
goto("/#");
|
||||
goto("/profiles");
|
||||
}}
|
||||
>
|
||||
{#await getUsername(uuid)}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
<script lang="ts">
|
||||
import { login } from "../components/stores";
|
||||
import { getProfiles, getUsername } from "../lib/api";
|
||||
|
||||
let profilesPromise: ReturnType<typeof getProfiles> = new Promise(() => {});
|
||||
$: if ($login !== undefined) {
|
||||
profilesPromise = getProfiles($login?.token);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Skyblock Data Tracker</title>
|
||||
</svelte:head>
|
||||
|
||||
<div id="list">
|
||||
{#await profilesPromise}
|
||||
<div class="item">
|
||||
<h2>Fetching profiles...</h2>
|
||||
</div>
|
||||
{:then profiles}
|
||||
{#each profiles as profile}
|
||||
<button type="button" class="item">
|
||||
{#each profile.data.members as member, idx}
|
||||
{#await getUsername(member)}
|
||||
<a href="/profile/{profile.profile}/member/{member}"
|
||||
>...</a
|
||||
>
|
||||
{:then username}
|
||||
<a href="/profile/{profile.profile}/member/{member}">
|
||||
{username}
|
||||
{#if idx === 0 && profile.data.name}
|
||||
@ {profile.data.name}
|
||||
{/if}
|
||||
</a>
|
||||
{:catch error}
|
||||
<p style="color: red">{error.message}</p>
|
||||
{/await}
|
||||
{/each}
|
||||
</button>
|
||||
{/each}
|
||||
<!-- svelte-ignore a11y-missing-content -->
|
||||
<a class="item add" href="/profile/new" />
|
||||
{:catch error}
|
||||
<div class="item">
|
||||
<h2 style="color: red">{error.message}</h2>
|
||||
</div>
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../styles/GridList";
|
||||
@use "../styles/GridItems";
|
||||
|
||||
.item {
|
||||
font-size: 1.17em;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
color: hsl(30, 20%, 90%);
|
||||
text-decoration: none;
|
||||
font-family: "Minecraft";
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
a:nth-child(1) {
|
||||
color: hsl(60, 100%, 50%);
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
.add {
|
||||
@include GridItems.add;
|
||||
}
|
||||
</style>
|
|
@ -7,6 +7,16 @@ import arangojs, { Database, aql } from "arangojs";
|
|||
import { hash } from "bcrypt";
|
||||
import { log } from "./logger";
|
||||
import { Permissions } from "./lib/permissions";
|
||||
import { stringify } from "json-bigint";
|
||||
|
||||
// HACK : BigInt support on JSON.stringify
|
||||
JSON.stringify = stringify;
|
||||
|
||||
export let profileNameCache: {
|
||||
[player: string]: {
|
||||
[profile: string]: string;
|
||||
};
|
||||
} = {};
|
||||
|
||||
export const {
|
||||
PORT,
|
||||
|
@ -71,7 +81,7 @@ const intervalFunc = async (db: Database) => {
|
|||
});
|
||||
await users.save({
|
||||
username: "_guest",
|
||||
permissions: [Permissions.VIEW]
|
||||
permissions: [Permissions.VIEW_PROFILES]
|
||||
});
|
||||
}
|
||||
if (!(await sessions.exists())) {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
@use "./Form";
|
||||
textarea {
|
||||
@include Form.input;
|
||||
}
|
|
@ -7,7 +7,7 @@ form {
|
|||
color: red;
|
||||
}
|
||||
}
|
||||
input {
|
||||
@mixin input {
|
||||
background: none;
|
||||
padding: 0.75em;
|
||||
margin-bottom: 1em;
|
||||
|
@ -22,6 +22,9 @@ input {
|
|||
border-color: black;
|
||||
}
|
||||
}
|
||||
input {
|
||||
@include input;
|
||||
}
|
||||
input[type="submit"] {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
@mixin add {
|
||||
position: relative;
|
||||
display: block;
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: min(12em, 30%);
|
||||
height: 0.3em;
|
||||
background-color: hsl(30, 20%, 90%);
|
||||
}
|
||||
&::before {
|
||||
transform: translate(-50%, -50%) rotateZ(90deg);
|
||||
}
|
||||
}
|
|
@ -22,21 +22,3 @@ div#list {
|
|||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.add {
|
||||
position: relative;
|
||||
display: block;
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: min(12em, 30%);
|
||||
height: 0.3em;
|
||||
background-color: hsl(30, 20%, 90%);
|
||||
}
|
||||
&::before {
|
||||
transform: translate(-50%, -50%) rotateZ(90deg);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue