Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
LauJosefsen committed Apr 11, 2024
1 parent 42dc44a commit d053308
Show file tree
Hide file tree
Showing 25 changed files with 697 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
node_modules/
9 changes: 9 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
DB_INSTANCES='mysql01,cego_test_db'
MYSQL01_HOST=127.0.0.1
MYSQL01_PORT=3306
MYSQL01_USER=root

CEGO_TEST_DB_HOST=127.0.0.1
CEGO_TEST_DB_PORT=3306
CEGO_TEST_DB_USER=root
CEGO_TEST_DB_DATABASE=testdb
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
39 changes: 39 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 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

# IDE
.idea
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}
28 changes: 28 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# use the official Bun image
# see all versions at https://hub.docker.com/r/oven/bun/tags
FROM oven/bun:1.1 as base
WORKDIR /usr/src/app

# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lockb /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile

# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lockb /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production

# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .

ENV NODE_ENV=production
RUN bun run build

CMD ["bun", "run", "start"]

29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,27 @@
# mysql-admin
Show, Edit or Create PROCESSLIST, SLAVE resources
This is a small Next.js app for viewing and killing active processes on multiple MariaDB instances.

### Features
- See all active processes, joined with any open transactions.
- Mark long-running open transactions with red.
- Sorted by transaction time desc, then by process time desc.
- Kill a process with two clicks.

### Deployment
TODO: Link to dockerhub

The image requires the following environment variables:
- `DB_INSTANCES` - A comma separated list of MariaDB instances to connect to.

For each instance, the following environment variables are required:
- `{DB_INSTANCE_NAME}_HOST` - The hostname of the MariaDB instance.
- `{DB_INSTANCE_NAME}_PORT` - The port of the MariaDB instance.
- `{DB_INSTANCE_NAME}_USER` - The username to connect to the MariaDB instance.

And optionally
- `{DB_INSTANCE_NAME}_PASSWORD` - The password to connect to the MariaDB instance.
- `{DB_INSTANCE_NAME}_DATABASE` - The database to connect to on the MariaDB instance.

Example environment variables can be found [here](.env).

### Development
To start a dev server, run `bun start dev`
Binary file added bun.lockb
Binary file not shown.
6 changes: 6 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}

export default nextConfig
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "mariadb-process-web",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start --port 80",
"lint": "next lint",
"prettier": "prettier --write ."
},
"dependencies": {
"dotenv": "^16.4.5",
"mysql2": "^3.9.4",
"next": "14.1.4",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20.12.7",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"daisyui": "^4.10.1",
"eslint": "^8",
"eslint-config-next": "14.1.4",
"postcss": "^8",
"prettier": "^3.2.5",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
6 changes: 6 additions & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Binary file added public/favicon.ico
Binary file not shown.
1 change: 1 addition & 0 deletions public/next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/vercel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 59 additions & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import assert from 'assert'

export type Instance = {
host: string
port: number
user: string
password: string | undefined
database: string | undefined
}

export type Instances = { [key: string]: Instance }

export type Config = {
instances: Instances
}

export const getConfig = (): Config => {
// Load database instances from the environment variables
const dbInstancesEnv = process.env.DB_INSTANCES
assert(dbInstancesEnv, 'DB_INSTANCES is required')
const dbInstances = dbInstancesEnv.split(',')

// For each db instance, load the host, port, user, password, and optional database
const instances: Instances = dbInstances.reduce((acc, dbInstance) => {
const dbInstanceUpper = dbInstance.toUpperCase()
const dbInstanceHostEnv = process.env[`${dbInstanceUpper}_HOST`]
assert(dbInstanceHostEnv, `${dbInstanceUpper}_HOST is required`)
const dbInstanceHost = dbInstanceHostEnv

const dbInstancePortEnv = process.env[`${dbInstanceUpper}_PORT`]
assert(dbInstancePortEnv, `${dbInstanceUpper}_PORT is required`)

const dbInstancePort = parseInt(dbInstancePortEnv)
assert(dbInstancePort, `${dbInstanceUpper}_PORT must be a number`)

const dbInstanceUserEnv = process.env[`${dbInstanceUpper}_USER`]
assert(dbInstanceUserEnv, `${dbInstanceUpper}_USER is required`)

const dbInstanceUser = dbInstanceUserEnv

const dbInstancePassword = process.env[`${dbInstanceUpper}_PASSWORD`]

const dbInstanceDatabase = process.env[`${dbInstanceUpper}_DATABASE`]

acc[dbInstance] = {
host: dbInstanceHost,
port: dbInstancePort,
user: dbInstanceUser,
password: dbInstancePassword,
database: dbInstanceDatabase,
}

return acc
}, {} as Instances)

return {
instances,
}
}
6 changes: 6 additions & 0 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import '@/styles/globals.css'
import type { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
13 changes: 13 additions & 0 deletions src/pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
13 changes: 13 additions & 0 deletions src/pages/api/hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'

type Data = {
name: string
}

export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
res.status(200).json({ name: 'John Doe' })
}
37 changes: 37 additions & 0 deletions src/pages/api/kill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Given the id in post body, kill the mysql process with that id

import { NextApiRequest, NextApiResponse } from 'next'
import { getConfig } from '@/lib/config'
import mysql from 'mysql2/promise'

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === 'POST') {
const { id, instance } = req.body
if (!id) {
return res.status(400).json({ error: 'id is required' })
}

if (!instance) {
return res.status(400).json({ error: 'instance is required' })
}

const dbConfig = getConfig().instances[instance]

if (!dbConfig) {
return res.status(400).json({ error: 'instance not found' })
}

// Kill the mysql process with the id
// Use the id to kill the mysql process
// res.status(200).json({ name: 'John Doe' });
const conn = await mysql.createConnection(dbConfig)
await conn.query(`KILL ?`, [id])
res.status(200).json({ id })
} else {
res.setHeader('Allow', ['POST'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}
37 changes: 37 additions & 0 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import { Config, getConfig } from '@/lib/config'

type Repo = {
instances: string[]
}

export const getServerSideProps = (async () => {
// Load available instances

const repo: Repo = { instances: Object.keys(getConfig().instances) }
// Pass data to the page via props
return { props: { repo } }
}) satisfies GetServerSideProps<{ repo: Repo }>

export default function Home({
repo,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<main>
<div className="text-xl breadcrumbs">
<ul>
<li>
<a>Instance Selector</a>
</li>
</ul>
</div>
<ul>
{repo.instances.map((instance) => (
<li key={instance}>
<a href={`/instance/${instance}`}>{instance}</a>
</li>
))}
</ul>
</main>
)
}
Loading

0 comments on commit d053308

Please sign in to comment.