Squashed commit of the following:
commit dd3c21375d3d5a74c4058c5017cd7b4e32039fe1
Author: Adam Veldhousen <adamveld12@gmail.com>
Date: Sun May 30 20:02:07 2021 -0500
added manifest file, cleaned up search and time chart
commit 75bb4e2ed29aae571853d7ae8815b4b80b5a2a40
Author: Adam Veldhousen <adamveld12@gmail.com>
Date: Sat May 29 21:46:47 2021 -0500
some big updates to how the pages are set up
commit 66611d5b2a0b338c11010a189a6e12264186e560
Author: Adam Veldhousen <adamveld12@gmail.com>
Date: Sat May 29 16:49:25 2021 -0500
cleanup, better dev experience, got recursors to show
commit c9b4a30d05d5f3be9ed3754051d2a26b905f5940
Author: Adam Veldhousen <adam.veldhousen@liveauctioneers.com>
Date: Sat May 29 15:33:02 2021 -0500
checkpoint
commit f8dc94effcbab16f1811c7ed313beb9b2834d3ed
Author: Adam Veldhousen <adam.veldhousen@liveauctioneers.com>
Date: Sat May 29 13:46:18 2021 -0500
add env vars to globals
commit acd44f7e53b62e3c673800423300cfdcaada6ee9
Author: Adam Veldhousen <adam.veldhousen@liveauctioneers.com>
Date: Sat May 29 13:39:25 2021 -0500
update build for frontend
commit 9ed14e0574c335645883f8151919e488b36f5a5a
Author: Adam Veldhousen <adamveld12@gmail.com>
Date: Fri May 28 18:20:34 2021 -0500
got go embed working with frontend
commit 56fe5d371fe6451e818ba65c2e2716b86c27eb91
Author: Adam Veldhousen <adamveld12@gmail.com>
Date: Thu May 27 16:27:58 2021 -0500
trying to add goembed
pull/1/head
parent
b102e17f72
commit
8b76101107
26
Dockerfile
26
Dockerfile
|
|
@ -1,26 +1,34 @@
|
|||
# ADMIN DASHBOARD BUILD
|
||||
FROM node:lts-alpine as build-client
|
||||
|
||||
COPY . /build/
|
||||
WORKDIR /build/client
|
||||
|
||||
RUN apk add --no-cache make
|
||||
|
||||
RUN make build-client
|
||||
COPY . /build/
|
||||
RUN npm install && npm run build && mkdir -p /build/.bin/static && cp -R ./public /build/.bin/static
|
||||
|
||||
# DNS/API SERVER BUILD
|
||||
FROM golang:alpine as build-server
|
||||
|
||||
WORKDIR /build/
|
||||
|
||||
RUN apk add --no-cache --update make gcc musl-dev
|
||||
|
||||
COPY --from=build-client /build /build
|
||||
|
||||
RUN apk add --no-cache make
|
||||
|
||||
RUN make .bin/gopherhole
|
||||
|
||||
|
||||
# RUNTIME ENVIRONMENT
|
||||
FROM alpine
|
||||
|
||||
RUN apl add --no-cache ca-certificates
|
||||
WORKDIR /opt
|
||||
|
||||
RUN addgroup -g 1000 gopherhole \
|
||||
&& adduser -H -D -u 1000 gopherhole gopherhole
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
RUN addgroup gopherhole \
|
||||
&& adduser -H -D gopherhole gopherhole \
|
||||
&& mkdir -p /data \
|
||||
&& chown -R gopherhole /data
|
||||
|
||||
COPY --chown=gopherhole:gopherhole --from=build-server /build/.bin/gopherhole /opt/gopherhole
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,42 +1,45 @@
|
|||
{
|
||||
"name": "svelte-app",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"start": "sirv public",
|
||||
"validate": "svelte-check"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||
"@rollup/plugin-typescript": "^8.1.0",
|
||||
"@tsconfig/svelte": "^1.0.0",
|
||||
"autoprefixer": "^10.2.3",
|
||||
"postcss": "^8.2.4",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-css-only": "^3.1.0",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
"svelte": "^3.38.2",
|
||||
"svelte-check": "^1.0.0",
|
||||
"svelte-preprocess": "^4.6.3",
|
||||
"tailwindcss": "^2.0.2",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"bootstrap": "^5.0.0",
|
||||
"chart.js": "^3.2.1",
|
||||
"date-fns": "^2.21.3",
|
||||
"fa-svelte": "^3.1.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"sirv-cli": "^1.0.0",
|
||||
"svelte-awesome": "^2.3.1",
|
||||
"svelte-chartjs": "^1.0.1",
|
||||
"svelte-routing": "^1.6.0",
|
||||
"sveltestrap": "^4.2.1"
|
||||
}
|
||||
"name": "svelte-app",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production rollup -c",
|
||||
"dev": "NODE_ENV=development rollup -c -w",
|
||||
"start": "sirv public --single -G -D",
|
||||
"validate": "svelte-check"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||
"@rollup/plugin-replace": "^2.4.2",
|
||||
"@rollup/plugin-typescript": "^8.1.0",
|
||||
"@tsconfig/svelte": "^1.0.0",
|
||||
"@types/node": "^15.6.1",
|
||||
"autoprefixer": "^10.2.3",
|
||||
"postcss": "^8.2.4",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-css-only": "^3.1.0",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
"sirv-cli": "^1.0.0",
|
||||
"svelte": "^3.38.2",
|
||||
"svelte-check": "^1.0.0",
|
||||
"svelte-preprocess": "^4.6.3",
|
||||
"tailwindcss": "^2.0.2",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"bootstrap": "^5.0.0",
|
||||
"chart.js": "^3.2.1",
|
||||
"date-fns": "^2.21.3",
|
||||
"fa-svelte": "^3.1.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"randomcolor": "^0.6.2",
|
||||
"svelte-awesome": "^2.3.1",
|
||||
"svelte-chartjs": "^1.0.1",
|
||||
"svelte-routing": "^1.6.0",
|
||||
"sveltestrap": "^4.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<title>Gopherhole: A customizable DNS server</title>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "Gopherhole Dashboard",
|
||||
"short_name": "Gopherhole",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "white",
|
||||
"theme_color": "rgb(55, 65, 81)",
|
||||
"description": "A DNS server that help's you take back control of your network",
|
||||
"icons": [
|
||||
{
|
||||
"src": "images/touch/homescreen48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/touch/homescreen72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/touch/homescreen96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/touch/homescreen144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/touch/homescreen168.png",
|
||||
"sizes": "168x168",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/touch/homescreen192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"related_applications": [
|
||||
{
|
||||
"platform": "play",
|
||||
"url": "https://play.google.com/store/apps/details?id=cheeaun.hackerweb"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package public
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *
|
||||
var Assets embed.FS
|
||||
|
|
@ -6,69 +6,83 @@ import { terser } from "rollup-plugin-terser";
|
|||
import sveltePreprocess from "svelte-preprocess";
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import css from "rollup-plugin-css-only";
|
||||
import replace from "@rollup/plugin-replace";
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
const isDev = Boolean(process.env.ROLLUP_WATCH);
|
||||
const production = !isDev;
|
||||
|
||||
function serve() {
|
||||
let server;
|
||||
let server;
|
||||
|
||||
function toExit() {
|
||||
if (server) server.kill(0);
|
||||
}
|
||||
function toExit() {
|
||||
console.log("~~~~ Shutting down dev server ~~~~");
|
||||
|
||||
return {
|
||||
writeBundle() {
|
||||
if (server) return;
|
||||
server = require("child_process").spawn(
|
||||
"npm",
|
||||
["run", "start", "--", "--dev"],
|
||||
{
|
||||
stdio: ["ignore", "inherit", "inherit"],
|
||||
shell: true,
|
||||
}
|
||||
);
|
||||
if (server) server.kill(0);
|
||||
}
|
||||
|
||||
process.on("SIGTERM", toExit);
|
||||
process.on("exit", toExit);
|
||||
},
|
||||
};
|
||||
return {
|
||||
writeBundle() {
|
||||
if (server) return;
|
||||
server = require("child_process").spawn(
|
||||
"npm",
|
||||
["run", "start", "--", "--dev"],
|
||||
{
|
||||
stdio: ["ignore", "inherit", "inherit"],
|
||||
shell: true,
|
||||
}
|
||||
);
|
||||
|
||||
process.on("SIGTERM", toExit);
|
||||
process.on("exit", toExit);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
input: "src/main.ts",
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: "iife",
|
||||
name: "app",
|
||||
file: "public/build/bundle.js",
|
||||
},
|
||||
plugins: [
|
||||
svelte({
|
||||
// add postcss config with tailwind
|
||||
preprocess: sveltePreprocess({
|
||||
postcss: {
|
||||
plugins: [require("tailwindcss"), require("autoprefixer")],
|
||||
},
|
||||
}),
|
||||
compilerOptions: {
|
||||
dev: !production,
|
||||
},
|
||||
}),
|
||||
css({ output: "bundle.css" }),
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: ["svelte"],
|
||||
}),
|
||||
commonjs(),
|
||||
typescript({
|
||||
sourceMap: !production,
|
||||
inlineSources: !production,
|
||||
}),
|
||||
!production && serve(),
|
||||
!production && livereload("public"),
|
||||
production && terser(),
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false,
|
||||
},
|
||||
input: "src/main.ts",
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: "iife",
|
||||
name: "app",
|
||||
// dir: "public/build/",
|
||||
file: "public/build/bundle.js",
|
||||
},
|
||||
plugins: [
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
include: ["src/**/*.ts", "src/**/*.svelte"],
|
||||
values: {
|
||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
||||
"process.env.API_HOST": JSON.stringify(
|
||||
isDev ? "http://localhost:8000" : ""
|
||||
),
|
||||
},
|
||||
}),
|
||||
svelte({
|
||||
// add postcss config with tailwind
|
||||
preprocess: sveltePreprocess({
|
||||
postcss: {
|
||||
plugins: [require("tailwindcss"), require("autoprefixer")],
|
||||
},
|
||||
}),
|
||||
compilerOptions: { dev: isDev },
|
||||
}),
|
||||
css({ output: "bundle.css", sourceMap: true }),
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: ["svelte"],
|
||||
}),
|
||||
commonjs(),
|
||||
typescript({
|
||||
sourceMap: true,
|
||||
inlineSources: isDev,
|
||||
cacheDir: "node_modules/.tmp/.rollup.tscache",
|
||||
}),
|
||||
isDev && serve(),
|
||||
isDev && livereload({ watch: "public", delay: 200 }),
|
||||
production && terser(),
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
faCloudUploadAlt,
|
||||
faTrafficLight,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import type { IconDefinition } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import Home from "./routes/Home.svelte";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
import { apiCall } from './util'
|
||||
import { getUnixTime, sub } from 'date-fns';
|
||||
|
||||
export interface Recursor {
|
||||
ipAddress: string
|
||||
timeoutMs: number
|
||||
weight: number
|
||||
}
|
||||
|
||||
export const getRecursors = async () => await apiCall<Recursor>('recursors')
|
||||
|
||||
|
||||
export interface Log {
|
||||
Started: string
|
||||
Domain: string
|
||||
ClientIP: string
|
||||
Status: string
|
||||
Protocol: string
|
||||
Error: string
|
||||
RecurseUpstreamIP: string
|
||||
RecurseRoundTripTimeMs: number
|
||||
TotalTimeMs: number
|
||||
};
|
||||
|
||||
export interface LogSearchOptions {
|
||||
start: Date
|
||||
end: Date
|
||||
page: number
|
||||
filter: string
|
||||
}
|
||||
|
||||
export const getLogs = async({
|
||||
start = sub(new Date(), { hours: 24 }),
|
||||
end = new Date(),
|
||||
page = 0,
|
||||
filter = ""
|
||||
}: LogSearchOptions) => await apiCall<Log>('metrics/log', 'GET', {
|
||||
filter,
|
||||
page,
|
||||
start: getUnixTime(start),
|
||||
end: getUnixTime(end),
|
||||
});
|
||||
|
||||
export interface StatsSearchOptions {
|
||||
start: Date
|
||||
end: Date
|
||||
key: StatSearchKey
|
||||
interval: number
|
||||
}
|
||||
|
||||
export enum StatSearchKey {
|
||||
Domain = "domain",
|
||||
ClientIp = "clientIp",
|
||||
Status = "status",
|
||||
Protocol = "protocol"
|
||||
}
|
||||
|
||||
export interface Stat {
|
||||
Header: string
|
||||
AverageTotalTime: Number
|
||||
Count: number,
|
||||
Time: string
|
||||
};
|
||||
|
||||
export const getStats = async ({
|
||||
start = sub(new Date(), { hours: 24 }),
|
||||
end = new Date(),
|
||||
key = StatSearchKey.Domain,
|
||||
interval = 30,
|
||||
}: StatsSearchOptions) => await apiCall<Stat>('metrics/stats', 'GET', {
|
||||
start: getUnixTime(start),
|
||||
end: getUnixTime(end),
|
||||
key,
|
||||
interval
|
||||
});
|
||||
|
||||
|
||||
export interface RuleAnswer {
|
||||
type: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface Rule {
|
||||
id: number
|
||||
weight: number
|
||||
enabled: boolean
|
||||
created: string
|
||||
name: string
|
||||
value: string
|
||||
answer: RuleAnswer
|
||||
ttl: number
|
||||
}
|
||||
|
||||
export const getRules = () => apiCall<Rule>('rules');
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { sub, getUnixTime } from 'date-fns'
|
||||
import { API_HOST } from './util';
|
||||
|
||||
|
||||
export const getLogs = async function ({
|
||||
start = sub(new Date(), { hours: 12 }),
|
||||
end = new Date(),
|
||||
page = 0,
|
||||
filter = ""
|
||||
} = {}) {
|
||||
try {
|
||||
const data = await fetch(API_HOST(`metrics/log?filter=${filter}&start=${getUnixTime(start)}&end=${getUnixTime(end)}&page=${page}`), {
|
||||
"method": "GET",
|
||||
"headers": { "Accept": "application/json" }
|
||||
});
|
||||
|
||||
const { success, payload } = await data.json();
|
||||
if (success) {
|
||||
return { payload, start, end, filter };
|
||||
}
|
||||
return { error: payload };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { error };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import { getUnixTime, sub, format } from 'date-fns';
|
||||
import { readable } from "svelte/store";
|
||||
import { API_HOST } from './util';
|
||||
|
||||
export const SearchDateFormatStr = "yyyy-MM-dd HH:mm:ss.SSS";
|
||||
export const formatDateForSearch = date => format(date, SearchDateFormatStr);
|
||||
|
||||
/*
|
||||
take something like `clientIp:127.0.0.1 protocol:udp domain:google.com`
|
||||
`(prop:expr)*
|
||||
*/
|
||||
export const parseTerms = (terms: string = "") :[string] | [] => {
|
||||
|
||||
const matches = terms.match(/([a-z]+[:>][\w\.]+)*/ig);
|
||||
|
||||
const exprs = matches.reduce((agg, term) => {
|
||||
|
||||
}, []);
|
||||
return [];
|
||||
};
|
||||
|
||||
export const fetchMetrics = async function ({
|
||||
start = sub(new Date(), { hours: 8 }),
|
||||
end = new Date(),
|
||||
key = "domain",
|
||||
interval = "30",
|
||||
} = {}) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
API_HOST(`metrics/stats?start=${getUnixTime(
|
||||
start
|
||||
)}&end=${getUnixTime(end)}&key=${key}&interval=${interval}`),
|
||||
{
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" }
|
||||
}
|
||||
);
|
||||
|
||||
const { success, payload } = await response.json();
|
||||
|
||||
if (!success) {
|
||||
return { error: payload };
|
||||
}
|
||||
|
||||
return { payload, interval, start, end, key };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return { error: e };
|
||||
}
|
||||
};
|
||||
|
||||
export const metricsStore = readable([], (set) => {
|
||||
const interval = setInterval(async () => {
|
||||
const { error, payload } = await fetchMetrics({});
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(payload);
|
||||
set(payload);
|
||||
}, 15 * 1000);
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { apiCall } from './util'
|
||||
|
||||
export interface Recursor {
|
||||
ipAddress: string
|
||||
timeoutMs: number
|
||||
weight: number
|
||||
}
|
||||
|
||||
export const getRecursors = () => apiCall<Recursor>('recursors')
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { apiCall } from './util'
|
||||
|
||||
export interface RuleAnswer {
|
||||
type: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface Rule {
|
||||
id: number
|
||||
weight: number
|
||||
enabled: boolean
|
||||
created: string
|
||||
name: string
|
||||
value: string
|
||||
answer: RuleAnswer
|
||||
ttl: number
|
||||
}
|
||||
|
||||
export const getRules = () => apiCall<Rule>('rules');
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
|
||||
import { sub, format, fromUnixTime } from 'date-fns';
|
||||
interface APIResponse<T> {
|
||||
success?: boolean
|
||||
payload?: T[]
|
||||
|
|
@ -5,11 +7,28 @@ interface APIResponse<T> {
|
|||
|
||||
}
|
||||
|
||||
export const API_HOST = (url = "") => `http://localhost:8000/api/v1/${url}`;
|
||||
const api_host = process.env.API_HOST;
|
||||
|
||||
export const apiCall = async function<T>(url: string, method: string = 'GET'): Promise<APIResponse<T>> {
|
||||
export const fromUnixTimeSafe = (value: string = ""): Date => {
|
||||
const n = Number(value);
|
||||
if (!n || isNaN(n)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fromUnixTime(n);
|
||||
};
|
||||
|
||||
export const createSearchDate = (start: Date = new Date(), hours: number = 0): Date => {
|
||||
const d = sub(start, { hours });
|
||||
d.setSeconds(0);
|
||||
return d;
|
||||
}
|
||||
export const buildQueryParams = (qps = null) => qps == null ? "" : `?${Object.keys(qps).filter(key => !!qps[key] && (qps[key] !== "undefined")).map(key => `${key}=${qps[key]}`).join('&')}`
|
||||
export const API_HOST = (url = "", queryParams) => `${api_host}/api/v1/${url}${buildQueryParams(queryParams)}`;
|
||||
|
||||
export const apiCall = async function <T>(url: string, method: string = 'GET', queryParams = null): Promise<APIResponse<T>> {
|
||||
try {
|
||||
const data = await fetch(`${API_HOST}/${url}`, {
|
||||
const data = await fetch(API_HOST(url, queryParams), {
|
||||
headers: { "Accept": "application/json" },
|
||||
method,
|
||||
});
|
||||
|
|
@ -27,3 +46,7 @@ export const apiCall = async function<T>(url: string, method: string = 'GET'): P
|
|||
return { error };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const SearchDateFormatStr = "yyyy-MM-dd HH:mm:ss";
|
||||
export const formatDateForSearch = date => format(date, SearchDateFormatStr);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { format, parse } from "date-fns";
|
||||
import { format, fromUnixTime, getUnixTime, parse } from "date-fns";
|
||||
|
||||
import {
|
||||
Input,
|
||||
|
|
@ -7,16 +7,13 @@
|
|||
InputGroupAddon,
|
||||
InputGroupText,
|
||||
} from "sveltestrap";
|
||||
import { SearchDateFormatStr } from "../api/metrics";
|
||||
import { SearchDateFormatStr } from "../api/util";
|
||||
|
||||
interface IDateTimeParts {
|
||||
date: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
export let label: string = "label";
|
||||
export let defaultValue: Date = new Date();
|
||||
|
||||
const fromDateTimeParts = ({ date, time }: IDateTimeParts): Date =>
|
||||
parse(`${date} ${time}`, SearchDateFormatStr, new Date());
|
||||
|
||||
|
|
@ -25,19 +22,24 @@
|
|||
time: format(dt, "HH:mm:ss"),
|
||||
});
|
||||
|
||||
const isDate = (v) => (v.match(/[\d]{4}-\d{2}-\d{2}/) || []).length > 0;
|
||||
const isDate = (v: string) =>
|
||||
(v.match(/[\d]{4}-\d{2}-\d{2}/) || []).length > 0;
|
||||
|
||||
let dateTimeParts = toDateTimeParts(defaultValue);
|
||||
export let label: string = "label";
|
||||
export let defaultValue: Date = new Date();
|
||||
export let value: Date = defaultValue;
|
||||
|
||||
const update = ({ target: { value } }) => {
|
||||
$: dateTimeParts = toDateTimeParts(value);
|
||||
|
||||
const update = ({ target: { value: v } }) => {
|
||||
const { date, time } = dateTimeParts;
|
||||
let dateTimePartsInput = isDate(value)
|
||||
? { date: value, time }
|
||||
: { date, time: value };
|
||||
|
||||
const updatedDateTime = fromDateTimeParts(dateTimePartsInput);
|
||||
dateTimeParts = toDateTimeParts(updatedDateTime);
|
||||
console.log(dateTimeParts);
|
||||
let dateTimePartsInput = isDate(v)
|
||||
? { date: v, time }
|
||||
: { date, time: v };
|
||||
|
||||
value = fromDateTimeParts(dateTimePartsInput);
|
||||
// dateTimeParts = toDateTimeParts(value);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { Column, Table } from "sveltestrap";
|
||||
import { getLogs } from "../api/logs";
|
||||
import type { Log } from "../api";
|
||||
|
||||
let rows = [];
|
||||
onMount(async () => {
|
||||
try {
|
||||
const { payload } = await getLogs();
|
||||
rows = payload || [];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
// export let page: number = 0;
|
||||
export let logs: Log[] = [];
|
||||
</script>
|
||||
|
||||
<div class="flex flex-column text-sm">
|
||||
{#if rows.length > 0}
|
||||
<Table {rows} let:row hover bordered>
|
||||
{#if logs && logs.length > 0}
|
||||
<Table rows={logs} let:row hover bordered>
|
||||
<Column header="Started">
|
||||
{row.Started}
|
||||
</Column>
|
||||
|
|
@ -49,6 +41,8 @@
|
|||
</Table>
|
||||
{:else}
|
||||
<p>No Logs yet!</p>
|
||||
<p><em>TODO:</em> Link to docs on how to point your router at this server</p>
|
||||
<p>
|
||||
<em>TODO:</em> Link to docs on how to point your router at this server
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { Column, Row, Table } from "sveltestrap";
|
||||
import { getRules } from "../api/rules";
|
||||
import type { Rule } from "../api/rules";
|
||||
import { getRules } from "../api";
|
||||
import type { Rule } from "../api";
|
||||
|
||||
let rows: Rule[] = [];
|
||||
onMount(async () => {
|
||||
|
|
|
|||
|
|
@ -1,51 +1,42 @@
|
|||
<script lang="ts">
|
||||
import sub from "date-fns/sub";
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
DropdownToggle,
|
||||
Form,
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
InputGroupText,
|
||||
Label,
|
||||
Input,
|
||||
} from "sveltestrap";
|
||||
import { StatSearchKey } from "../api";
|
||||
import DatetimePicker from "./DatetimePicker.svelte";
|
||||
|
||||
export let start: Date = sub(new Date(), { hours: 24 });
|
||||
export let end: Date = new Date();
|
||||
export let key: StatSearchKey = StatSearchKey.Domain;
|
||||
export let filter: string = "";
|
||||
|
||||
$: rawFilter = filter;
|
||||
|
||||
let menuItem = [
|
||||
{ label: "Domain Name", key: "domain" },
|
||||
{ label: "Client IP", key: "clientIp" },
|
||||
{ label: "Protocol", key: "protocol" },
|
||||
{ label: "Status", key: "status" },
|
||||
{ label: "Domain Name", key: StatSearchKey.Domain },
|
||||
{ label: "Client IP", key: StatSearchKey.ClientIp },
|
||||
{ label: "Protocol", key: StatSearchKey.Protocol },
|
||||
{ label: "Status", key: StatSearchKey.Status },
|
||||
];
|
||||
|
||||
let selected = menuItem[0];
|
||||
const selectItem = (selectedItem) => {
|
||||
selected = selectedItem;
|
||||
handleOnChange();
|
||||
$: selected = !!key ? menuItem.find((v) => v.key === key) : menuItem[0];
|
||||
|
||||
const selectItem = (selectedItem) => (key = selectedItem.key);
|
||||
|
||||
const onFilterChanged = ({ target: { value } }) => {
|
||||
console.log(value);
|
||||
filter = value;
|
||||
};
|
||||
|
||||
let searchFilter = "";
|
||||
let startDate = new Date();
|
||||
let endDate = new Date();
|
||||
|
||||
const handleOnChange = () => {
|
||||
if (onChange) {
|
||||
const event = {
|
||||
terms: searchFilter,
|
||||
start: startDate,
|
||||
end: endDate,
|
||||
key: selected.key,
|
||||
};
|
||||
|
||||
onChange(event);
|
||||
}
|
||||
};
|
||||
|
||||
export let onChange = (evt) =>
|
||||
console.log("no handler bound to SearchOptions.svelte: ", evt);
|
||||
</script>
|
||||
|
||||
<div class="w-full flex flex-row">
|
||||
|
|
@ -55,17 +46,16 @@
|
|||
<InputGroupAddon addonType="prepend">
|
||||
<InputGroupText>Search</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
<Input
|
||||
id="search"
|
||||
type="search"
|
||||
on:change={handleOnChange}
|
||||
bind:value={searchFilter}
|
||||
/>
|
||||
<Input id="search" type="search" on:change={onFilterChanged} />
|
||||
</InputGroup>
|
||||
</FormGroup>
|
||||
<FormGroup class="flex flex-row">
|
||||
<DatetimePicker label="Start" />
|
||||
<DatetimePicker label="End" />
|
||||
<DatetimePicker
|
||||
label="Start"
|
||||
defaultValue={start}
|
||||
bind:value={start}
|
||||
/>
|
||||
<DatetimePicker label="End" defaultValue={end} bind:value={end} />
|
||||
</FormGroup>
|
||||
<FormGroup class="mx-2">
|
||||
<Dropdown>
|
||||
|
|
|
|||
|
|
@ -1,45 +1,22 @@
|
|||
<script>
|
||||
import { fetchMetrics } from "../api/metrics.ts";
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import type { Stat } from "../api";
|
||||
import randomColor from "randomcolor";
|
||||
|
||||
import { Chart, registerables } from "chart.js";
|
||||
Chart.register(...registerables);
|
||||
|
||||
let canvas;
|
||||
onMount(async () => {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const { labels, datasets } = await fetchAndPrepareMetrics();
|
||||
export let stats: Stat[] = [];
|
||||
|
||||
var c = new Chart(ctx, {
|
||||
type: "line",
|
||||
data: { labels, datasets },
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
title: {
|
||||
display: true,
|
||||
text: `Top Requests`,
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
stacked: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const fetchAndPrepareMetrics = async () => {
|
||||
const { error, payload } = await fetchMetrics({ key: "clientIp" });
|
||||
if (error) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const chartData = payload.reduce((agg, x) => {
|
||||
const transformStats = (ostats) => {
|
||||
const chartData = ostats.reduce((agg, x) => {
|
||||
let root = agg[x.Header] || {
|
||||
labels: [],
|
||||
dataset: {
|
||||
label: x.Header,
|
||||
borderColor: "rgb(75,192,192)",
|
||||
borderColor: randomColor({
|
||||
luminosity: "dark",
|
||||
}), //"rgb(75,192,192)",
|
||||
data: [],
|
||||
},
|
||||
};
|
||||
|
|
@ -50,15 +27,108 @@
|
|||
}, {});
|
||||
|
||||
const finalChartData = Object.keys(chartData).map((x) => chartData[x]);
|
||||
const finalChartLabels = finalChartData[0].labels;
|
||||
|
||||
const finalChartLabels =
|
||||
finalChartData.length > 0 ? finalChartData[0].labels : [];
|
||||
return {
|
||||
labels: finalChartLabels,
|
||||
datasets: finalChartData.map((x) => x.dataset),
|
||||
};
|
||||
};
|
||||
|
||||
const generateChartOptions = (s: [], empty: Boolean = false) => {
|
||||
let labels = [];
|
||||
let datasets = [];
|
||||
|
||||
if (s && s.length > 0) {
|
||||
({ labels, datasets } = transformStats(s));
|
||||
}
|
||||
|
||||
var delayed;
|
||||
return {
|
||||
type: "line",
|
||||
data: {
|
||||
labels,
|
||||
datasets,
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
// x: {
|
||||
// type: "time",
|
||||
// ticks: {
|
||||
// source: "auto",
|
||||
// // Disabled rotation for performance
|
||||
// maxRotation: 0,
|
||||
// autoSkip: true,
|
||||
// },
|
||||
// },
|
||||
y: {
|
||||
stacked: true,
|
||||
},
|
||||
},
|
||||
hoverRadius: 5,
|
||||
interaction: {
|
||||
mode: "nearest",
|
||||
intersect: false,
|
||||
axis: "x",
|
||||
},
|
||||
plugins: {
|
||||
decimation: {
|
||||
enabled: true,
|
||||
algorithm: "lttb",
|
||||
samples: 60,
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
radius: {
|
||||
duration: 150,
|
||||
easing: "linear",
|
||||
loop: (c) => c.active,
|
||||
},
|
||||
},
|
||||
// animation: {
|
||||
// onComplete: () => {
|
||||
// delayed = true;
|
||||
// },
|
||||
// delay: (context) => {
|
||||
// let delay = 0;
|
||||
// if (
|
||||
// context.type === "data" &&
|
||||
// context.mode === "default" &&
|
||||
// !delayed
|
||||
// ) {
|
||||
// delay =
|
||||
// context.dataIndex * 30 +
|
||||
// context.datasetIndex * 10;
|
||||
// }
|
||||
// return delay;
|
||||
// },
|
||||
// },
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
let canvas = null;
|
||||
let chartInstance = null;
|
||||
|
||||
onMount(async () => {
|
||||
const ctx = canvas.getContext("2d");
|
||||
chartInstance = new Chart(ctx, generateChartOptions(stats, true));
|
||||
});
|
||||
|
||||
const update = (s) => {
|
||||
if (chartInstance) {
|
||||
const { options, data } = generateChartOptions(s, false);
|
||||
chartInstance.options = options;
|
||||
chartInstance.data = data;
|
||||
chartInstance.update();
|
||||
}
|
||||
};
|
||||
|
||||
$: update(stats);
|
||||
</script>
|
||||
|
||||
<div style="position: relative; max-height: 512px; height: 512px; width: 100%;">
|
||||
<div style="position: relative; max-height: 384px; height: 384px; width: 100%;">
|
||||
<canvas width="100%" height="100%" bind:this={canvas} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ const app = new App({
|
|||
target: document.body,
|
||||
});
|
||||
|
||||
|
||||
export default app;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,128 @@
|
|||
<script lang="ts">
|
||||
import LogViewer from "../components/LogViewer.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { navigate } from "svelte-routing";
|
||||
import { getUnixTime, isEqual, sub } from "date-fns";
|
||||
|
||||
import { buildQueryParams, fromUnixTimeSafe } from "../api/util";
|
||||
import { getLogs, getStats, StatSearchKey } from "../api";
|
||||
import type { Stat, Log } from "../api";
|
||||
|
||||
import PageContainer from "./PageContainer.svelte";
|
||||
import SearchOptions from "../components/SearchOptions.svelte";
|
||||
import TimeChart from "../components/TimeChart.svelte";
|
||||
import PageContainer from "./PageContainer.svelte";
|
||||
import LogViewer from "../components/LogViewer.svelte";
|
||||
|
||||
export let location: Location;
|
||||
|
||||
const { search } = location;
|
||||
let params = new URLSearchParams(search.substring(1));
|
||||
|
||||
export let start: Date =
|
||||
fromUnixTimeSafe(params.get("start")) || sub(new Date(), { hours: 24 });
|
||||
export let end: Date = fromUnixTimeSafe(params.get("end")) || new Date();
|
||||
|
||||
export let filter: string = params.get("filter") || "";
|
||||
export let chartKey: StatSearchKey =
|
||||
StatSearchKey[params.get("key")] || StatSearchKey.Domain;
|
||||
export let chartInterval: number = 30;
|
||||
export let logPage: number = 0;
|
||||
|
||||
let logErrorMsg: string = null;
|
||||
let chartErrorMsg: string = null;
|
||||
let chartDataLoading: Boolean = true;
|
||||
let logDataLoading: Boolean = true;
|
||||
|
||||
let chartData: Stat[] = [];
|
||||
let logData: Log[] = [];
|
||||
|
||||
const fetchLogs = async () => {
|
||||
if (logDataLoading) {
|
||||
console.warn("tried loading logs while already loading");
|
||||
}
|
||||
logErrorMsg = null;
|
||||
logDataLoading = true;
|
||||
const { error, payload } = await getLogs({
|
||||
start,
|
||||
end,
|
||||
page: logPage,
|
||||
filter,
|
||||
});
|
||||
logDataLoading = false;
|
||||
|
||||
if (error) {
|
||||
logErrorMsg = error;
|
||||
return [];
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
const fetchStats = async () => {
|
||||
if (chartDataLoading) {
|
||||
console.warn("tried loading stats while already loading");
|
||||
}
|
||||
chartErrorMsg = null;
|
||||
chartDataLoading = true;
|
||||
const { error, payload } = await getStats({
|
||||
start,
|
||||
end,
|
||||
key: chartKey,
|
||||
interval: chartInterval,
|
||||
});
|
||||
chartDataLoading = false;
|
||||
|
||||
if (error) {
|
||||
chartErrorMsg = error;
|
||||
return [];
|
||||
}
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
const updateData = async (evt) => {
|
||||
console.groupCollapsed("Stats Data Update");
|
||||
const { filter: eFilter, start: eStart, end: eEnd, key: eKey } = evt;
|
||||
|
||||
navigate(
|
||||
`${location?.pathname}${buildQueryParams({
|
||||
start: getUnixTime(eStart),
|
||||
end: getUnixTime(eEnd),
|
||||
filter: eFilter,
|
||||
key: eKey,
|
||||
})}`,
|
||||
{ replace: true }
|
||||
);
|
||||
|
||||
[logData, chartData] = await Promise.all([fetchLogs(), fetchStats()]);
|
||||
console.info("handled search, fetching new data:", evt);
|
||||
console.groupEnd();
|
||||
};
|
||||
|
||||
$: updateData({ start, end, key: chartKey, filter });
|
||||
</script>
|
||||
|
||||
<PageContainer
|
||||
{location}
|
||||
header="Stats"
|
||||
description="Server performance stats and activity log"
|
||||
>
|
||||
<SearchOptions />
|
||||
<section class="my-5">
|
||||
<TimeChart />
|
||||
<SearchOptions bind:start bind:end bind:filter bind:key={chartKey} />
|
||||
<section style="height: 384px;" class="my-5">
|
||||
{#if chartDataLoading}
|
||||
<p>Loading chart</p>
|
||||
{:else if chartErrorMsg}
|
||||
<p>{chartErrorMsg}</p>
|
||||
{:else}
|
||||
<TimeChart stats={chartData} />
|
||||
{/if}
|
||||
</section>
|
||||
<section class="my-5">
|
||||
<LogViewer />
|
||||
{#if logDataLoading}
|
||||
<p>Loading logs</p>
|
||||
{:else if logErrorMsg}
|
||||
<p>{logErrorMsg}</p>
|
||||
{:else}
|
||||
<LogViewer logs={logData} />
|
||||
{/if}
|
||||
</section>
|
||||
</PageContainer>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import { fade, fly } from "svelte/transition";
|
||||
export let header: string = "Home";
|
||||
export let description: string = "";
|
||||
export let location: Location;
|
||||
|
||||
onMount(() => console.log(`Page mounted ${location.pathname}`));
|
||||
</script>
|
||||
|
||||
<section out:fade={{ duration: 100 }} class="w-full">
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
import { onMount } from "svelte";
|
||||
import { Column, Row, Table } from "sveltestrap";
|
||||
import { getRecursors } from "../api/recursors";
|
||||
import type { Recursor } from "../api/recursors";
|
||||
import { getRecursors } from "../api";
|
||||
import type { Recursor } from "../api";
|
||||
|
||||
let rows: Recursor[] = [];
|
||||
onMount(async () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
|
||||
"types": ["node"],
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -1,6 +1,6 @@
|
|||
module github.com/adamveld12/gopherhole
|
||||
|
||||
go 1.15
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi v1.5.4
|
||||
|
|
|
|||
BIN
gopherhole
BIN
gopherhole
Binary file not shown.
|
|
@ -1,8 +1,10 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
|
|
@ -26,7 +28,7 @@ type adminHandler struct {
|
|||
h http.Handler
|
||||
}
|
||||
|
||||
func NewAdminHandler(c Cache, s Storage, re *RuleEngine) http.Handler {
|
||||
func NewAdminHandler(c Cache, s Storage, re *RuleEngine, content fs.FS) http.Handler {
|
||||
handler := chi.NewRouter()
|
||||
|
||||
a := &adminHandler{
|
||||
|
|
@ -39,20 +41,34 @@ func NewAdminHandler(c Cache, s Storage, re *RuleEngine) http.Handler {
|
|||
handler.Use(middleware.RequestID)
|
||||
handler.Use(middleware.RealIP)
|
||||
handler.Use(middleware.Logger)
|
||||
handler.Use(middleware.AllowContentType("application/json; utf-8", "application/json"))
|
||||
handler.Use(middleware.Timeout(time.Second * 5))
|
||||
|
||||
handler.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"http://*", "https://*"},
|
||||
AllowedMethods: []string{"GET", "PUT", "DELETE", "POST", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Content-Type", "Accept"},
|
||||
AllowCredentials: false,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
|
||||
handler.Use(middleware.Recoverer)
|
||||
handler.Use(middleware.Timeout(time.Second * 5))
|
||||
// TODO: smarter way https://github.com/go-chi/chi/issues/403
|
||||
|
||||
// handler.Handle("/build/", http.StripPrefix("/build/", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
// file, err := content.Open(fmt.Sprintf("client/public/build/%s", r.URL.Path))
|
||||
// if err != nil {
|
||||
// rw.WriteHeader(http.StatusNotFound)
|
||||
// return
|
||||
// }
|
||||
// defer file.Close()
|
||||
|
||||
// if _, err := io.Copy(rw, file); err != nil {
|
||||
// rw.WriteHeader(http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
|
||||
// })))
|
||||
|
||||
handler.Route("/api/v1", func(r chi.Router) {
|
||||
r.Use(middleware.AllowContentType("application/json; utf-8", "application/json"))
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"http://*", "https://*"},
|
||||
AllowedMethods: []string{"GET", "PUT", "DELETE", "POST", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Content-Type", "Accept"},
|
||||
AllowCredentials: false,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
r.Get("/metrics/log", RestHandler(a.getLog).ToHF())
|
||||
r.Get("/metrics/stats", RestHandler(a.getStats).ToHF())
|
||||
|
||||
|
|
@ -79,6 +95,23 @@ func NewAdminHandler(c Cache, s Storage, re *RuleEngine) http.Handler {
|
|||
r.HandleFunc("/signal", a.signal)
|
||||
})
|
||||
|
||||
fs := http.FS(content)
|
||||
httpFileServer := http.FileServer(fs)
|
||||
handler.Handle("/*", http.StripPrefix("/", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
f, err := content.Open(r.URL.Path)
|
||||
if os.IsNotExist(err) {
|
||||
r.URL.Path = "/"
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
f.Close()
|
||||
}
|
||||
|
||||
log.Printf("%s - err: %v", r.URL.Path, err)
|
||||
|
||||
httpFileServer.ServeHTTP(rw, r)
|
||||
})))
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func (ss *Sqlite) GetRecursors() ([]RecursorRow, error) {
|
|||
|
||||
defer rows.Close()
|
||||
|
||||
var results []RecursorRow
|
||||
results := []RecursorRow{}
|
||||
for rows.Next() {
|
||||
var row RecursorRow
|
||||
|
||||
|
|
|
|||
4
main.go
4
main.go
|
|
@ -10,6 +10,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/adamveld12/gopherhole/client/public"
|
||||
"github.com/adamveld12/gopherhole/internal"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
|
@ -19,6 +20,7 @@ var (
|
|||
// dbPath = flag.String("db-path", ".", "Directory to write database files to")
|
||||
// httpAddr = flag.String("http-address", ":8080", "Bind address for http server")
|
||||
// dnsAddr = flag.String("dns-address", ":53", "Bind address for dns server")
|
||||
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
@ -69,7 +71,7 @@ func main() {
|
|||
}
|
||||
}()
|
||||
|
||||
httpApi := internal.NewAdminHandler(cache, store, re)
|
||||
httpApi := internal.NewAdminHandler(cache, store, re, public.Assets)
|
||||
if err := http.ListenAndServe(conf.HTTPAddr, httpApi); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
35
makefile
35
makefile
|
|
@ -1,17 +1,16 @@
|
|||
build: clobber .bin/client/public .bin/gopherhole
|
||||
|
||||
dev: clean .bin/gopherhole .bin/config.json
|
||||
cd .bin && ./gopherhole -config config.json
|
||||
|
||||
client-dev: client/node_modules
|
||||
cd ./client && npm run dev
|
||||
|
||||
client/node_modules:
|
||||
cd ./client && npm install
|
||||
|
||||
clean:
|
||||
@rm -rf .bin/gopherhole .bin/config.json
|
||||
@rm -rf .bin/gopherhole .bin/config.json .bin/client
|
||||
|
||||
clobber:
|
||||
@rm -rf .bin ./client/node_modules
|
||||
clobber: clean
|
||||
@rm -rf .bin ./client/node_modules ./client/public/build
|
||||
|
||||
vdhsn/gopherhole:
|
||||
docker build -t vdhsn/gopherhole:latest .
|
||||
|
||||
test:
|
||||
dig -p 5353 twitter.com @localhost
|
||||
|
|
@ -19,14 +18,28 @@ test:
|
|||
dig -p 5353 loki.veldhousen.ninja @localhost
|
||||
dig -p 5353 www.liveauctioneers.com @localhost
|
||||
|
||||
.PHONY: clean clobber dev
|
||||
|
||||
.PHONY: build clean clobber client-dev dev test vdhsn/gopherhole
|
||||
|
||||
.bin:
|
||||
mkdir -p .bin
|
||||
|
||||
.bin/gopherhole: .bin
|
||||
# @go build --tags "sqlite_foreign_keys fts5" -v -o .bin/gopherhole .
|
||||
# @go build --tags "sqlite_foreign_keys fts5" -v -o .bin/gopherhole .
|
||||
@go build --tags "fts5" -v -o .bin/gopherhole .
|
||||
|
||||
.bin/config.json:
|
||||
@cp ./config.example.json .bin/config.json
|
||||
|
||||
client-dev: client/node_modules
|
||||
cd ./client && npm run dev
|
||||
|
||||
.bin/client/public: .bin client/public/build
|
||||
mkdir -p .bin/client/public
|
||||
cp -R ./client/public/ .bin/client/
|
||||
|
||||
client/public/build: client/node_modules
|
||||
cd ./client && npm run build
|
||||
|
||||
client/node_modules:
|
||||
cd ./client && npm install
|
||||
|
|
|
|||
Loading…
Reference in New Issue