-
+
- + Instance Selector + +
-
+ {repo.instances.map((instance) => (
+
- + {instance} + + ))} +
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..2d7ec5c
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+.env
+node_modules/
diff --git a/.env b/.env
new file mode 100644
index 0000000..6c40fb1
--- /dev/null
+++ b/.env
@@ -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
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..1c2aa65
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..24c756b
--- /dev/null
+++ b/.gitignore
@@ -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
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..e74ed9f
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "trailingComma": "es5",
+ "tabWidth": 4,
+ "semi": false,
+ "singleQuote": true
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..45ed792
--- /dev/null
+++ b/Dockerfile
@@ -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"]
+
diff --git a/README.md b/README.md
index da7cb19..9ceb008 100644
--- a/README.md
+++ b/README.md
@@ -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`
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 0000000..06c8142
Binary files /dev/null and b/bun.lockb differ
diff --git a/next.config.mjs b/next.config.mjs
new file mode 100644
index 0000000..3cf2d80
--- /dev/null
+++ b/next.config.mjs
@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: true,
+}
+
+export default nextConfig
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..be8218a
--- /dev/null
+++ b/package.json
@@ -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"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..fef1b22
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/next.svg b/public/next.svg
new file mode 100644
index 0000000..5174b28
--- /dev/null
+++ b/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
new file mode 100644
index 0000000..d2f8422
--- /dev/null
+++ b/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/lib/config.ts b/src/lib/config.ts
new file mode 100644
index 0000000..3a5808d
--- /dev/null
+++ b/src/lib/config.ts
@@ -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,
+ }
+}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
new file mode 100644
index 0000000..322fb21
--- /dev/null
+++ b/src/pages/_app.tsx
@@ -0,0 +1,6 @@
+import '@/styles/globals.css'
+import type { AppProps } from 'next/app'
+
+export default function App({ Component, pageProps }: AppProps) {
+ return
🔥 | +Id | +User | +Host | +db | +Command | +Time | +State | +Info | +Progress | +Transaction Time | +Transaction Info | +
---|---|---|---|---|---|---|---|---|---|---|---|
+ {/* Make kill button with skull emoji, on click send a request to /api/kill with the id in the body */} + + | +{item.Id} | +
+
+ {item.User}
+
+ |
+ {item.Host} | +
+
+ {item.db}
+
+ |
+ + {item.Command} + | ++ {item.Time} s + | ++ {item.State} + | +{item.Info} | ++ {item.Progress} + | ++ {item.transaction?.activeTime} + {item.transaction?.activeTime + ? ' s' + : ''} + | ++ {item.transaction?.info.join('\n')} + | +
🔥 | +Id | +User | +Host | +db | +Command | +Time | +State | +Info | +Progress | +Transaction Time | +Transaction Info | +