Compare commits

..

2 Commits

Author SHA1 Message Date
d64cb69f1e pocketbase login 2023-09-20 17:03:50 +02:00
6a675c28bc pb 2023-09-20 14:03:05 +02:00
12 changed files with 132 additions and 54 deletions

15
package-lock.json generated
View File

@@ -9,14 +9,15 @@
"version": "0.0.0",
"dependencies": {
"@heroicons/vue": "^2.0.18",
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
"@vueuse/core": "^10.2.1",
"@vueuse/integrations": "^10.2.1",
"axios": "^1.4.0",
"pocketbase": "^0.18.0",
"vue": "^3.3.4",
"vue-router": "^4.2.4"
},
"devDependencies": {
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
"@types/node": "^20.4.5",
"@vitejs/plugin-vue": "^4.2.3",
"autoprefixer": "^10.4.14",
@@ -496,7 +497,6 @@
"version": "0.0.0-insiders.565cd3e",
"resolved": "https://registry.npmjs.org/@tailwindcss/nesting/-/nesting-0.0.0-insiders.565cd3e.tgz",
"integrity": "sha512-WhHoFBx19TnH/c+xLwT/sxei6+4RpdfiyG3MYXfmLaMsADmVqBkF7B6lDalgZD9YdM459MF7DtxVbWkOrV7IaQ==",
"dev": true,
"dependencies": {
"postcss-nested": "^5.0.5"
},
@@ -508,7 +508,6 @@
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
"integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
"dev": true,
"dependencies": {
"postcss-selector-parser": "^6.0.6"
},
@@ -1156,7 +1155,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"bin": {
"cssesc": "bin/cssesc"
},
@@ -1730,6 +1728,11 @@
"node": ">= 6"
}
},
"node_modules/pocketbase": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.18.0.tgz",
"integrity": "sha512-09ri0Rnm4JjboU4OJeibd6pgvKi4DPg/r/Uu/QI3mKSZsrROoMT75zyiOldbBBMWZUDG1TRlv6BjQj30SFsrVw=="
},
"node_modules/postcss": {
"version": "8.4.29",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz",
@@ -1845,7 +1848,6 @@
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -2151,8 +2153,7 @@
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/vite": {
"version": "4.4.9",

View File

@@ -9,11 +9,12 @@
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
"@heroicons/vue": "^2.0.18",
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
"@vueuse/core": "^10.2.1",
"@vueuse/integrations": "^10.2.1",
"axios": "^1.4.0",
"pocketbase": "^0.18.0",
"vue": "^3.3.4",
"vue-router": "^4.2.4"
},

View File

@@ -1,13 +1,21 @@
<script setup lang="ts">
import { RouterView } from 'vue-router';
import { RouterView, useRoute } from 'vue-router';
import { Sidebar } from './sidebar';
const route = useRoute();
</script>
<template>
<Sidebar />
<div class="main-container">
<template v-if="route.name === 'login'">
<RouterView />
</div>
</template>
<template v-else>
<Sidebar />
<div class="main-container">
<RouterView />
</div>
</template>
</template>
<style scoped>

View File

@@ -1,15 +1,25 @@
import { providePocketBase } from '@/pocketbase';
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import { routes } from './routes';
import './style.css';
const app = createApp(App);
const pb = providePocketBase(app);
const router = createRouter({
history: createWebHistory(),
routes,
});
const app = createApp(App);
router.beforeEach(async to => {
if (!pb.authStore.isValid && to.name !== 'login') {
return { name: 'login' };
} else if (pb.authStore.isValid && to.name === 'login') {
return { name: 'home' };
}
});
app.use(router);
app.mount('#app');

View File

@@ -2,7 +2,7 @@
import { formatIsk, percentFormater } from '@/formaters';
import { MarketType, MarketTypeLabel } from "@/market";
import { SortableHeader, useSort } from '@/table';
import { ArrowPathIcon, ShoppingCartIcon } from '@heroicons/vue/24/outline';
import { ShoppingCartIcon } from '@heroicons/vue/24/outline';
import { useStorage } from '@vueuse/core';
import { computed, ref } from 'vue';
import { ScanResult, getHistoryQuartils } from '.';
@@ -24,8 +24,6 @@ interface Props {
}
interface Emits {
(e: 'relaod', type: MarketType): void;
(e: 'relaodAll'): void;
(e: 'buy', type: MarketType, buy: number, sell: number): void;
}
@@ -84,9 +82,6 @@ const getLineColor = (result: Result) => {
<span>Filter: </span>
<input type="search" class="w-96" v-model="filter" >
</div>
<div class="end">
<button class="flex" @click="$emit('relaodAll')"><ArrowPathIcon class="stroke-slate-100 sh-6 w-6 me-2" />Reload all</button>
</div>
</div>
</div>
<table>
@@ -115,7 +110,6 @@ const getLineColor = (result: Result) => {
<td class="text-right">{{ percentFormater.format(r.profit) }}</td>
<td class="text-right">
<button class="btn-icon-stroke me-1" @click="$emit('buy', r.type, r.buy, r.sell)"><ShoppingCartIcon /></button>
<button class="btn-icon-stroke me-1" @click="$emit('relaod', r.type)"><ArrowPathIcon /></button>
</td>
</tr>
</tbody>

30
src/pages/Login.vue Normal file
View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { usePocketBase } from '@/pocketbase';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const pb = usePocketBase();
const router = useRouter();
const username = ref("");
const password = ref("");
const login = async () => {
await pb.collection('users').authWithPassword(username.value, password.value);
await router.push('/');
}
</script>
<template>
<div class="p-4 mx-auto mt-10 grid justify-center gap-2 w-64">
<div class="grid">
Login:
<input type="text" autocomplete="username" v-model="username" />
</div>
<div class="grid">
Password:
<input type="password" autocomplete="password" v-model="password" />
</div>
<button class="justify-self-end" @click="login" >Login</button>
</div>
</template>

View File

@@ -1,24 +1,21 @@
<script setup lang="ts">
import { MarketOrderHistory, MarketType, MarketTypePrice, getHistory, getMarketType, getMarketTypes, getPrice, getPrices, jitaId } from "@/market";
import { MarketType, MarketTypePrice, getHistory, getMarketType, getMarketTypes, getPrice, getPrices, jitaId } from "@/market";
import { BuyModal, ScanResult, ScanResultTable } from '@/market/scan';
import { useStorage } from '@vueuse/core';
import { usePocketBase } from "@/pocketbase";
import { onMounted, ref, watch } from 'vue';
type MarketItemStorage = {
typeID: number;
history: MarketOrderHistory[];
const pb = usePocketBase();
type MarketScan = {
id?: string;
owner: string;
types: number[];
}
const buyModal = ref<typeof BuyModal>();
const item = ref("");
/**
* @deprecated use itemsStorage instead
*
* TODO: remove this in the future
*/
const oldStorage = useStorage<MarketItemStorage[]>('market-items', []);
const itemsStorage = useStorage<MarketItemStorage[]>('market-scan-items', []);
const items = ref<ScanResult[]>([]);
const addOrRelaod = async (type: MarketType) => {
const typeID = type.id;
@@ -39,9 +36,6 @@ const addOrRelaod = async (type: MarketType) => {
items.value = [ ...items.value, item];
}
}
const reloadAll = async () => {
items.value = await Promise.all(items.value.map( async i => ({ ...i, history: await getHistory(jitaId, i.type.id) })));
}
const addItem = async () => {
const type = await getMarketType(item.value.split('\t')[0]);
@@ -49,26 +43,33 @@ const addItem = async () => {
addOrRelaod(type);
}
watch(items, itms => itemsStorage.value = itms.map(i => ({ typeID: i.type.id, history: i.history })));
onMounted(async () => {
const getMarketScan = () => pb.collection('marketScans').getFirstListItem<MarketScan>("").catch(() => null);
if (itemsStorage.value.length === 0) {
if (oldStorage.value.length > 0) {
itemsStorage.value = oldStorage.value;
oldStorage.value = [];
} else {
return;
}
watch(items, async itms => {
const types = itms.map(i => i.type.id);
const marketScan = await getMarketScan();
if (marketScan?.id) {
pb.collection('marketScans').update(marketScan.id, { owner: pb.authStore.model!.id, types });
} else {
pb.collection('marketScans').create({ owner: pb.authStore.model!.id, types });
}
});
onMounted(async () => {
const marketScan = await getMarketScan();
if (!marketScan || marketScan.types.length === 0) {
return;
}
const prices = await getPrices(await getMarketTypes(itemsStorage.value.map(i => i.typeID)));
items.value = itemsStorage.value.map(i => {
const price = prices.find(p => p.type.id === i.typeID) as MarketTypePrice;
const prices = await getPrices(await getMarketTypes(marketScan.types));
items.value = await Promise.all(marketScan.types.map(async i => {
const price = prices.find(p => p.type.id === i) as MarketTypePrice;
const history = await getHistory(jitaId, i);
return { ...i, ...price };
});
return { id: i, history, ...price };
}));
});
</script>
@@ -83,7 +84,7 @@ onMounted(async () => {
</div>
<template v-if="items.length > 0">
<hr />
<ScanResultTable :items="items" @relaod="type => addOrRelaod(type)" @relaodAll="reloadAll" @buy="(type, buy, sell) => buyModal?.open(type, { 'Buy': buy, 'Sell': sell })" />
<ScanResultTable :items="items" @buy="(type, buy, sell) => buyModal?.open(type, { 'Buy': buy, 'Sell': sell })" />
<BuyModal ref="buyModal" />
</template>
</template>

1
src/pocketbase/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './pocketbase';

View File

@@ -0,0 +1,13 @@
import PocketBase from 'pocketbase';
import { App, inject } from 'vue';
const pocketBaseSymbol = Symbol('pocketBase');
export const providePocketBase = (app: App) => {
const pb = new PocketBase('/pocketbase/');
app.provide(pocketBaseSymbol, pb);
return pb;
}
export const usePocketBase = () => inject<PocketBase>(pocketBaseSymbol)!;

View File

@@ -1,7 +1,8 @@
import { RouteRecordRaw } from 'vue-router';
export const routes: RouteRecordRaw[] = [
{ path: '/', component: () => import('@/pages/Index.vue') },
{ path: '/', name: 'home', component: () => import('@/pages/Index.vue') },
{ path: '/login', name: 'login', component: () => import('@/pages/Login.vue') },
{ path: '/reprocess', component: () => import('@/pages/Reprocess.vue') },
{ path: '/market', component: () => import('@/pages/Market.vue'), children: [
{ path: '', redirect: '/market/scan' },

View File

@@ -1,16 +1,25 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router';
import { usePocketBase } from '@/pocketbase';
import { RouterLink, useRouter } from 'vue-router';
const links = [
{ name: "Market", path: "/market" },
{ name: "Reprocess", path: "/reprocess" },
{ name: "Tools", path: "/tools" }
];
const pb = usePocketBase();
const router = useRouter();
const logout = async () => {
pb.authStore.clear();
await router.push({ name: 'login' });
}
</script>
<template>
<aside class="fixed top-0 left-0 w-64 h-screen transition-transform -translate-x-full sm:translate-x-0">
<div class="h-full px-3 py-4 overflow-y-auto bg-slate-700">
<div class="h-full px-3 py-4 overflow-y-auto bg-slate-700 flex flex-col">
<ul class="space-y-2 font-medium">
<li v-for="link in links" :key="link.name">
<RouterLink :to="link.path" class="flex items-center p-2 rounded-md hover:bg-slate-800">
@@ -18,6 +27,9 @@ const links = [
</RouterLink>
</li>
</ul>
<div class="mt-auto">
<button @click="logout">Logout</button>
</div>
</div>
</aside>
</template>

View File

@@ -24,6 +24,12 @@ export default defineConfig(({ mode }) => {
followRedirects: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
'/pocketbase/': {
target: `https://${env.POCKET_BASE_URL}/`,
changeOrigin: true,
followRedirects: true,
rewrite: (path) => path.replace(/^\/pocketbase/, ''),
},
'/appraisal/': {
target: `https://${env.EVEPRAISAL_URL}/`,
changeOrigin: true,