added admin panel

pull/1/head
Adam Veldhousen 11 months ago
parent 67d76a30ca
commit 6b4232a9b9
Signed by: adam
GPG Key ID: 6DB29003C6DD1E4B

@ -64,6 +64,40 @@ helm_resource(
labels=["9-data"]
)
helm_resource(
'kafka-ui',
'kafka-ui-github/kafka-ui',
namespace='barretthousen-local',
flags=[
'--set', 'envs.config.KAFKA_CLUSTERS_0_NAME=bh-kafka',
'--set', 'envs.config.KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092'
],
port_forwards=[port_forward(9090, 8080, name='kafka-ui')],
resource_deps=['kafka'],
labels=["9-data"]
)
def bh_client(service="", port_forwards=[], labels=['2-services'], deps=['ingress']):
basepath = './src/{}-client'.format(service)
docker_build(
ref='barretthousen/client-{}-client'.format(service),
context=basepath,
dockerfile=basepath +'/Dockerfile.dev-frontend'.format(service),
target='development',
entrypoint='vite dev --port=80 --host=0.0.0.0 --strictPort --logLevel info',
live_update=[
sync(basepath + '/src', '/opt/{}-client/src'.format(service)),
sync(basepath + '/static', '/opt/{}-client/static'.format(service)),
run('cd {} && npm install'.format(basepath), trigger=[basepath+'/package.json', basepath+'/package.lock.json'])
]
)
k8s_resource(
workload='{}-client-local'.format(service),
port_forwards=port_forwards,
labels=['2-services'],
resource_deps=deps
)
def bh_backend_service(service="", port_forwards=[], migrateDB=False, devMode=True, labels=['2-services'], deps=['postgres']):
local_resource(
@ -74,6 +108,7 @@ def bh_backend_service(service="", port_forwards=[], migrateDB=False, devMode=Tr
labels=['3-compilation']
)
# complains about grpc port still being in use, so maybe the service isn't exiting cleanly
entry_cmd = [
'/go/bin/dlv',
'--headless',
@ -111,7 +146,7 @@ def bh_backend_service(service="", port_forwards=[], migrateDB=False, devMode=Tr
],
live_update=[
sync('./.bin', '/opt'),
sync('./src', '/go/src')
sync('./src', '/go/src'),
]
)
@ -132,18 +167,6 @@ k8s_resource(
labels='2-services'
)
docker_build(
ref='barretthousen/service-web-client',
context='./src/web-client',
dockerfile='./src/web-client/Dockerfile.dev-frontend',
target='development'
)
k8s_resource(
workload='web-client-local',
port_forwards=['8080:80'],
labels=['2-services'],
resource_deps=['ingress']
)
bh_backend_service(service="runner", migrateDB=True, port_forwards=[
@ -162,22 +185,5 @@ bh_backend_service(service="proxy-client", port_forwards=[
port_forward(8081, 80, name="HTTP API @ localhost:8081")
], deps=['ingress'])
# local_resource(
# 'dev-web-client',
# dir='./src/web-client',
# cmd='npm i',
# serve_dir='./src/web-client',
# serve_cmd='npm run dev',
# ignore=['./src/web-client/src'],
# deps=[
# './src/web-client/package.json',
# './src/web-client/svelte.config.js',
# './src/web-client/vite.config.ts'
# ],
# readiness_probe=probe(10, 2, 10, 1, 3, http_get=http_get_action(8080, 'localhost', 'http')),
# links=[
# link(url='http://localhost:8080', name='Web Client')
# ],
# labels=['2-services']
# )
bh_client(service='web')
bh_client(service='admin')

@ -0,0 +1,41 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: admin-client
spec:
selector:
matchLabels:
app: admin-client
template:
metadata:
labels:
app: admin-client
spec:
serviceAccountName: barretthousen-service
containers:
- name: admin-client
image: barretthousen/client-admin-client:latest
imagePullPolicy: Always
stdin: true
tty: true
env:
- name: ORIGIN
value: https://admin.barretthousen.com
ports:
- containerPort: 80
name: http
resources:
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: admin-client
spec:
selector:
app: admin-client
ports:
- port: 80
targetPort: 80

@ -16,3 +16,10 @@ spec:
name: proxy-admin
port:
number: 80
- pathType: ImplementationSpecific
path: "/"
backend:
service:
name: admin-client
port:
number: 80

@ -5,7 +5,8 @@ resources:
- ./runner-deployment.yaml
- ./proxy-admin-deployment.yaml
- ./proxy-client-deployment.yaml
- ./sync-cronjob.yaml
- ./web-client-deployment.yaml
- ./admin-client-deployment.yaml
- ./client-ingress.yaml
- ./admin-ingress.yaml
- ./scrape-cronjob.yaml

@ -14,7 +14,7 @@ spec:
serviceAccountName: barretthousen-service
containers:
- name: web-client
image: barretthousen/service-web-client:latest
image: barretthousen/client-web-client:latest
imagePullPolicy: Always
stdin: true
tty: true

@ -6,7 +6,7 @@ nameSuffix: -beta
namespace: barretthousen-beta
patchesStrategicMerge:
- scrape-cronjob.yaml
- sync-cronjob.yaml
- catalog-secret.yaml
- proxy-admin-secret.yaml
- proxy-client-secret.yaml

@ -10,7 +10,7 @@ namespace: barretthousen-local
patchesStrategicMerge:
- debug-catalog.yaml
- debug-runner.yaml
- scrape-cronjob.yaml
- sync-cronjob.yaml
patches:
- target:

@ -6,7 +6,7 @@ commonLabels:
namespace: barretthousen
patchesStrategicMerge:
- scrape-cronjob.yaml
- sync-cronjob.yaml
- catalog-configmap.yaml
- proxy-admin-configmap.yaml
- proxy-client-configmap.yaml

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

@ -0,0 +1,30 @@
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};

@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

@ -0,0 +1,9 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

@ -0,0 +1,9 @@
FROM node:lts AS development
COPY . /opt/admin-client
WORKDIR /opt/admin-client
RUN npm install && npm install -g vite
CMD vite dev --port=80 --host=0.0.0.0 --strictPort --logLevel info

@ -0,0 +1,42 @@
FROM node:lts AS build
ARG origin=https://barretthousen.com
ENV PROTOCOL_HEADER=x-forwarded-proto
ENV HOST_HEADER=x-forwarded-host
ENV ORIGIN=${origin}
WORKDIR /opt/admin-client
COPY . /opt/admin-client
RUN npm run build \
&& cp -v /opt/admin-client/package.json /opt/admin-client/build \
&& cp -v /opt/admin-client/package-lock.json /opt/admin-client/build \
&& cd /opt/admin-client/build && npm ci --omit dev
FROM node:lts AS production
LABEL com.barretthousen.service="admin-client"
LABEL com.barretthousen.tags="frontend,public"
LABEL com.barretthousen.release-date="2023-05-01"
LABEL com.barretthousen.version="alpha-0.0.1"
LABEL com.barretthousen.git-ref="000000000000000000000000000000000000000000000000"
ARG origin=https://barretthousen.com
ENV ENV=production
ENV PROTOCOL_HEADER=x-forwarded-proto
ENV HOST_HEADER=x-forwarded-host
ENV ORIGIN=${origin}
WORKDIR /opt
COPY --from=build /opt/admin-client/build/ /opt/admin-client/
ENV PORT 80
EXPOSE 80
ENTRYPOINT ["node", "admin-client"]

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,44 @@
{
"name": "adminb-client",
"version": "0.0.1",
"private": true,
"main": "index.js",
"scripts": {
"dev": "vite dev --port 8080",
"build": "vite build",
"preview": "vite preview",
"test": "playwright test",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test:unit": "vitest",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-node": "^1.2.4",
"@sveltejs/kit": "^1.5.0",
"@types/luxon": "^3.3.0",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.26.0",
"less": "^4.1.3",
"postcss": "^8.4.23",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.54.0",
"svelte-check": "^3.0.1",
"tailwindcss": "^3.3.2",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.3.0",
"vitest": "^0.25.3"
},
"type": "module",
"dependencies": {
"luxon": "^3.3.0"
}
}

@ -0,0 +1,12 @@
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/
};
export default config;

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

@ -0,0 +1,28 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body,
html {
margin: 0;
padding: 0;
background: #222222;
color: #FFFFFF;
}
button,
input {
background: #222222;
border: #C2A661 solid 1px;
color: #FFFFFF;
}
a:hover {
color: #ebca78;
}
a {
color: #C2A661;
text-decoration: underline;
}

@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {};

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

@ -0,0 +1,7 @@
import { describe, it, expect } from 'vitest';
describe('sum test', () => {
it('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
});

@ -0,0 +1,35 @@
<script lang="ts">
import { DateTime } from 'luxon';
interface ScrapeJob {
id: Number;
targetSiteName: string;
createdTs: string;
completedTs: string;
auctionsFound: Number;
errors: string;
}
export let job: ScrapeJob;
const { id, targetSiteName, createdTs, completedTs, auctionsFound, errors } = job;
const createdDT = DateTime.fromISO(createdTs);
const completedDT = DateTime.fromISO(completedTs);
</script>
<article class="scrape-job-result flex flex-col py-3 shadow-sm border-b-2 border-bh-gold m-2">
<header class="pb-2">
Job #{id}
</header>
<section>
<ul>
<li class="text-sm">Target: {targetSiteName}</li>
<li class="text-sm">Started: {createdDT?.toLocaleString(DateTime.DATETIME_FULL)}</li>
{#if completedTs !== null}
<li class="text-sm">Completed: {completedDT?.toLocaleString(DateTime.DATETIME_FULL)}</li>
{/if}
<li class="text-sm">Found: {auctionsFound}</li>
<li class="text-sm">Errors: {errors}</li>
</ul>
</section>
</article>

@ -0,0 +1,20 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
let target: string = 'All';
const dispatch = createEventDispatcher();
function execScrape() {
dispatch('scrape', { target });
}
</script>
<form class="flex rounded-md" on:submit|preventDefault={() => execScrape()}>
<select name="target" class="bg-bh-gold text-bh-black text-center px-4" bind:value={target}>
<option>All</option>
<option>liveauctioneers</option>
</select>
<button class="border-none rounded-r-md bg-bh-gold text-bh-black py-1 px-2">Start Sync</button>
</form>

@ -0,0 +1,48 @@
<script lang="ts">
import '../app.css';
</script>
<svelte:head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>BH Admin Console</title>
<meta name="description" content="" />
</svelte:head>
<main class="flex flex-col w-full">
<nav
class="flex w-full bg-bh-gold text-bh-black fixed top-0 left-0 right-0"
style="height: 80px;"
>
<img
class="pr-5"
style="width: 450px; height: 80px;"
src="/logo_black_gold.svg"
alt="BarrettHousen"
/>
<ul class="flex w-full py-3 items-center justify-between">
<li class="flex grow justify-center">
<span class="flex grow" style="max-width: 75%;"> Admin Stuff </span>
</li>
<li class="px-6">
<!-- <span>I want email alerts!</span> -->
</li>
</ul>
</nav>
<div class="flex flex-row flex-grow h-full" style="padding-top: 80px;">
<div id="gutter" class="h-full" style="min-width: 195px; max-width: 195px;" />
<section id="content" class="py-6 pl-4 grow border-l border-bh-gold" style="min-height: 100vh;">
<slot />
</section>
</div>
<footer
class="flex justify-center bg-bh-gold text-bh-black w-full py-10 shadow-inner shadnow-md shadow-bh-black"
>
<p>
We find great auctions of rare and one of a kind collectibles from around the web and compile
them in one easy place.
</p>
</footer>
</main>

@ -0,0 +1,11 @@
import type { LayoutLoad } from './$types';
export const load = (({ url }) => {
return {
// TODO: refactor to one source of truth for all query param values
query: url.searchParams.get('query') || '',
page: parseInt(url.searchParams.get('page') || '0'),
limit: parseInt(url.searchParams.get('pageSize') || '64'),
};
}) satisfies LayoutLoad;

@ -0,0 +1,61 @@
<script lang="ts">
import type { PageData } from './$types';
import { fade } from 'svelte/transition';
import { invalidateAll } from '$app/navigation';
import StartScrapeForm from '$lib/StartScrapeForm.svelte';
import ScrapeJobResult from '$lib/ScrapeJobResult.svelte';
export let data: PageData;
$: completedJobs = data.jobs.filter(({ completedTs }) => completedTs !== null);
$: activeJobs = data.jobs.filter(({ completedTs }) => completedTs === null);
$: activeJobCount = activeJobs.length;
$: completedJobCount = completedJobs.length;
async function onScrape({ detail }) {
try {
const response = await fetch(
new Request('/api/v1/sync', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ targetSite: detail.target })
})
);
const scrapeJob = await response.json();
data.jobs.push(scrapeJob);
console.log(scrapeJob);
} catch (error) {}
}
</script>
<section class="flex w-full flex-col justify-center" in:fade out:fade>
<h1 class="text-2xl mb-8">Sync Status</h1>
<div class="mb-8">
<StartScrapeForm on:scrape={onScrape} />
</div>
<section>
<h2>{activeJobCount} In Progress Jobs</h2>
<ul class="flex">
{#each activeJobs as j, i}
<li in:fade>
<ScrapeJobResult job={j} />
</li>
<!-- {#if i < 10}
{/if} -->
{/each}
</ul>
<h2>{completedJobCount} Complete</h2>
<ul class="flex flex-wrap justify-between">
{#each completedJobs as j, i}
<li in:fade>
<ScrapeJobResult job={j} />
</li>
<!-- {#if i < 5}
{/if} -->
{/each}
</ul>
</section>
</section>

@ -0,0 +1,32 @@
import { browser } from '$app/environment';
import type { PageLoad } from './$types';
// TODO: change to env var
const API_HOST = `${browser ? '' : 'http://proxy-admin-local'}/api/v1`;
interface Job {
id: Number;
targetSiteName: string;
createdTs: string;
completedTs: string;
auctionsFound: Number;
errors: string;
}
interface ScrapeStatusPageData {
jobs: Job[]
}
export const load = (async ({ fetch, url }): Promise<ScrapeStatusPageData> => {
try {
const response = await fetch(API_HOST + `/sync`);
const {results } = await response.json();
return {
jobs: results || []
};
} catch (e) {
console.log(e);
return { jobs:[] };
}
}) satisfies PageLoad;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

@ -0,0 +1,23 @@
import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
env: {
publicPrefix: 'BH_WEB_CLIENT'
},
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({
precompress: true
})
}
};
export default config;

@ -0,0 +1,13 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {
colors: {
"bh-gold": "#C2A661",
"bh-black": "#222222",
}
}
},
plugins: []
};

@ -0,0 +1,8 @@
import { expect, test } from '@playwright/test';
test('Start Scape control exists', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('textbox', { name: 'target' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Run Job' })).toBeVisible();
});

@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

@ -0,0 +1,9 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
});
Loading…
Cancel
Save