Skip to content

Commit

Permalink
feat: initial clickhouse-monitoring
Browse files Browse the repository at this point in the history
  • Loading branch information
duyet committed Nov 18, 2023
0 parents commit 5a6bffa
Show file tree
Hide file tree
Showing 58 changed files with 7,110 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CLICKHOUSE_HOST=http://localhost:8123
CLICKHOUSE_USER=default
CLICKHOUSE_PASSWORD=
27 changes: 27 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"extends": [
"next/core-web-vitals",
"plugin:tailwindcss/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier"
],
"plugins": ["tailwindcss"],
"rules": {
"@next/next/no-html-link-for-pages": "off",
"tailwindcss/no-custom-classname": "off",
"tailwindcss/classnames-order": "error",
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/no-unknown-property": "off"
},
"settings": {
"tailwindcss": {
"callees": ["cn", "cva"],
"config": "tailwind.config.cjs"
},
"next": {
"rootDir": ["app/*/"]
}
}
}
64 changes: 64 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Build

on:
push:
branches:
- main
pull_request:
types:
- opened
- reopened
workflow_dispatch:

env:
CLICKHOUSE_HOST: http://localhost:8123
CLICKHOST_USER: default
CLICKHOST_PASS: ""

permissions:
contents: read
pages: write
id-token: write

jobs:
build:
runs-on: ubuntu-latest
services:
clickhouse:
image: clickhouse/clickhouse-server:23.10
ports:
- 8123:8123
- 9000:9000
options: >-
--health-cmd "wget --no-verbose --tries=1 --spider http://localhost:8123/?query=SELECT%201 || exit 1"
--health-interval 30s
--health-timeout 10s
--health-retries 5
--health-start-period 30s
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 20
cache: yarn

- name: Restore cache
uses: actions/cache@v3
with:
path: |
.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
- name: Install dependencies
run: yarn install

- name: Build Next.js
run: yarn build
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
12 changes: 12 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Build artifacts
.next/
.turbo/
_next/
__tmp__/
dist/
node_modules/
target/
compiled/
pnpm-lock.yaml
.github/actions/*/index.mjs
test/**/out/**
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# ClickHouse Monitoring Dashboard

This is a simple monitoring dashboard for ClickHouse cluster, built with [Next.js](https://nextjs.org/).

## Getting Started

To get the project up and running on your local machine, follow these steps:

1. Clone the repository
2. Install dependencies with `npm install` or `yarn install`
3. Run the development server with `npm run dev` or `yarn dev`

Open [http://localhost:3000](http://localhost:3000) in your browser to see the dashboard.

## Project Structure

The main page of the dashboard can be edited by modifying `app/page.tsx`. Changes to the file will automatically update the page.

## Deployment

For easy deployment, use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme), created by the makers of Next.js. Refer to the [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

## Feedback and Contributions

Feedback and contributions are welcome! Feel free to open issues or submit pull requests.

## License

MIT. See [LICENSE](LICENSE) for more details.
10 changes: 10 additions & 0 deletions app/[name]/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { UpdateIcon } from '@radix-ui/react-icons'

export default function Loading() {
return (
<div className="flex flex-row items-center gap-3">
<UpdateIcon className="h-4 w-4 animate-spin" />
Loading...
</div>
)
}
64 changes: 64 additions & 0 deletions app/[name]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { unstable_noStore as noStore } from 'next/cache'

import { fetchData } from '@/lib/clickhouse'
import { getQueryByName, queries } from '@/lib/clickhouse-queries'
import { cn } from '@/lib/utils'
import { DataTable } from '@/components/data-table/data-table'

interface PageProps {
params: {
name: string
}
}

export const dynamic = 'force-dynamic'
export const revalidate = 5

export default async function Page({ params: { name } }: PageProps) {
noStore()

// Get the query config
const config = getQueryByName(name)
if (!config) {
return <div>404</div>
}

// Fetch the data from ClickHouse
const data = await fetchData(config.sql)

// Related charts
const charts = []
if (config.relatedCharts) {
for (const chart of config.relatedCharts) {
const chartsModule = await import(`@/components/charts/${chart}`)
charts.push(chartsModule.default)
}
}

const chartWidth = charts.length > 1 ? `w-1/${charts.length}` : 'w-full'

return (
<div className="flex flex-col">
{charts.length > 0 ? (
<div className="mb-5 flex flex-row gap-5">
{charts.map((Chart, i) => (
<Chart
key={i}
className={cn(chartWidth, 'p-0 shadow-none')}
chartClassName="h-44"
/>
))}
</div>
) : null}

<div>
<DataTable title={name.replace('-', ' ')} config={config} data={data} />
</div>
</div>
)
}

export const generateStaticParams = async () =>
queries.map(({ name }) => ({
name,
}))
48 changes: 48 additions & 0 deletions app/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use client'

import {
createContext,
Dispatch,
SetStateAction,
useContext,
useState,
} from 'react'

import type { ClickHouseIntervalFunc } from '@/components/interval-select'

export interface ContextValue {
interval: ClickHouseIntervalFunc
setInterval?: Dispatch<SetStateAction<ClickHouseIntervalFunc>>
reloadInterval: number | null
setReloadInterval: Dispatch<SetStateAction<number | null>>
}

export const Context = createContext<ContextValue | undefined>(undefined)

export const AppProvider = ({ children }: { children: React.ReactNode }) => {
const [interval, setInterval] = useState<ClickHouseIntervalFunc>(
'toStartOfFiveMinutes'
)

// Set reload interval to 5 seconds by default
// setReloadInterval(null) to stop it
const [reloadInterval, setReloadInterval] = useState<number | null>(5000)

return (
<Context.Provider
value={{ interval, setInterval, reloadInterval, setReloadInterval }}
>
{children}
</Context.Provider>
)
}

export const useAppContext = () => {
const context = useContext(Context)

if (context === undefined) {
throw new Error('useAppContext must be used within a AppProvider')
}

return context
}
Binary file added app/favicon.ico
Binary file not shown.
50 changes: 50 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--primary: 47.9 95.8% 53.1%;
--primary-foreground: 26 83.3% 14.1%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
}

.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 47.9 95.8% 53.1%;
--primary-foreground: 26 83.3% 14.1%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 35.5 91.7% 32.9%;
}
}
33 changes: 33 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'

import '@/app/globals.css'

import { Header } from '@/components/header'
import { AppProvider } from '@/app/context'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
title: 'ClickHouse Monitoring',
description: 'Simple UI for ClickHouse Monitoring',
}

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
<AppProvider>
<div className="hidden h-full flex-1 flex-col space-y-8 p-8 md:flex">
<Header />
{children}
</div>
</AppProvider>
</body>
</html>
)
}
Loading

0 comments on commit 5a6bffa

Please sign in to comment.