Development Guide ​
Contributing to Minepanel or running it in development mode.
Getting Started ​
Prerequisites ​
Before you start, make sure you have:
- Node.js 18+ and npm
- Docker and Docker Compose
- Git
- Code editor (VS Code recommended)
Clone the Repository ​
git clone https://github.com/Ketbome/minepanel.git
cd minepanelProject Structure ​
minepanel/
├── backend/ # NestJS backend
│ ├── src/
│ │ ├── auth/ # Authentication module
│ │ ├── server-management/ # Server management
│ │ ├── docker-compose/ # Docker integration
│ │ ├── app.module.ts # Root module
│ │ └── main.ts # Entry point
│ ├── test/ # E2E tests
│ ├── package.json
│ └── tsconfig.json
│
├── frontend/ # Next.js frontend
│ ├── src/
│ │ ├── app/ # Next.js 14 App Router
│ │ ├── components/ # React components
│ │ ├── lib/ # Utilities & hooks
│ │ └── services/ # API clients
│ ├── public/ # Static assets
│ ├── package.json
│ └── tsconfig.json
│
├── docker-compose.yml # Production compose
├── docker-compose.split.yml # Split services
├── Dockerfile # Multi-stage build
└── README.mdDevelopment Setup ​
Option 1: Local Development (Recommended) ​
Run frontend and backend directly on your machine for the best development experience.
Step 1: Install Dependencies ​
Backend:
cd backend
npm installFrontend:
cd frontend
npm installStep 2: Configure Environment ​
Backend - Create backend/.env:
# Backend environment
SERVERS_DIR=../servers
FRONTEND_URL=http://localhost:3000
CLIENT_USERNAME=admin
CLIENT_PASSWORD=$2a$12$kvlrbEjbVd6SsbD8JdIB.OOQWXTPL5dFgo5nDeIXgeW.BhIyy8ocu
DEFAULT_LANGUAGE=enFrontend - Create frontend/.env.local:
NEXT_PUBLIC_BACKEND_URL=http://localhost:8091
NEXT_PUBLIC_FILEBROWSER_URL=http://localhost:8080
NEXT_PUBLIC_DEFAULT_LANGUAGE=enStep 3: Create Servers Directory ​
# From project root
mkdir -p serversStep 4: Start Development Servers ​
Terminal 1 - Backend:
cd backend
npm run start:devTerminal 2 - Frontend:
cd frontend
npm run devTerminal 3 - Filebrowser (optional):
docker run -d \
--name filebrowser-dev \
-p 8080:8080 \
-v $(pwd)/servers:/data \
-v ./filebrowser-data:/config \
hurlenko/filebrowserStep 5: Access the Application ​
- Frontend: http://localhost:3000
- Backend API: http://localhost:8091
- Filebrowser: http://localhost:8080
Hot Reload
Both frontend and backend have hot reload enabled. Changes are reflected immediately!
Option 2: Docker Development ​
Use Docker Compose for a production-like environment.
# Build images
docker compose build
# Start services
docker compose up
# Or in detached mode
docker compose up -d
# View logs
docker compose logs -fDevelopment Workflow ​
Making Changes ​
Create a feature branch
bashgit checkout -b feature/your-feature-nameMake your changes
- Edit code in
backend/orfrontend/ - Follow the coding standards (below)
- Edit code in
Test your changes
bash# Backend tests cd backend npm run test npm run test:e2e # Frontend (tests coming soon) cd frontend npm run build # Check for build errorsCommit your changes
bashgit add . git commit -m "feat: add new feature"Push and create PR
bashgit push origin feature/your-feature-name # Then create a Pull Request on GitHub
Coding Standards ​
TypeScript ​
- Use TypeScript for all new code
- Enable strict mode
- Define interfaces for all data structures
- Avoid
any- use proper types
Example:
// ❌ Bad
function createServer(config: any) {
// ...
}
// âś… Good
interface ServerConfig {
name: string;
type: ServerType;
version: string;
memory: string;
port: number;
}
function createServer(config: ServerConfig) {
// ...
}React Components ​
- Use functional components with hooks
- Use TypeScript for props
- Extract reusable logic into custom hooks
- Keep components small and focused
Example:
// âś… Good component
interface ServerCardProps {
server: Server;
onStart: (id: string) => void;
onStop: (id: string) => void;
}
export function ServerCard({ server, onStart, onStop }: ServerCardProps) {
return (
<div className="card">
<h3>{server.name}</h3>
<button onClick={() => onStart(server.id)}>Start</button>
<button onClick={() => onStop(server.id)}>Stop</button>
</div>
);
}File Naming ​
- React components: PascalCase (
ServerCard.tsx) - Hooks: camelCase with
useprefix (useServerStatus.tsx) - Services: camelCase (
authService.ts) - Utils: camelCase (
formatBytes.ts)
Code Style ​
We use ESLint and Prettier for code formatting.
# Backend
cd backend
npm run lint
npm run format
# Frontend
cd frontend
npm run lint
npm run formatBackend Development ​
NestJS Architecture ​
Minepanel backend follows NestJS best practices:
src/
├── main.ts # Bootstrap
├── app.module.ts # Root module
├── auth/ # Feature module
│ ├── auth.module.ts # Module definition
│ ├── auth.controller.ts # HTTP endpoints
│ ├── auth.service.ts # Business logic
│ ├── jwt.strategy.ts # JWT strategy
│ └── local.strategy.ts # Local strategy
└── server-management/ # Feature module
├── server-management.module.ts
├── server-management.controller.ts
├── server-management.service.ts
└── dto/
└── server-config.model.tsCreating a New Module ​
cd backend
nest generate module my-feature
nest generate controller my-feature
nest generate service my-featureAdding an Endpoint ​
Controller:
import { Controller, Get, Post, Body, Param } from "@nestjs/common";
@Controller("api/servers")
export class ServerManagementController {
constructor(private readonly serverService: ServerManagementService) {}
@Get()
async getAllServers() {
return this.serverService.listServers();
}
@Post()
async createServer(@Body() config: CreateServerDto) {
return this.serverService.createServer(config);
}
@Get(":id")
async getServer(@Param("id") id: string) {
return this.serverService.getServer(id);
}
}Service:
import { Injectable } from "@nestjs/common";
@Injectable()
export class ServerManagementService {
async listServers() {
// Implementation
}
async createServer(config: CreateServerDto) {
// Implementation
}
async getServer(id: string) {
// Implementation
}
}Testing Backend ​
# Unit tests
npm run test
# E2E tests
npm run test:e2e
# Test coverage
npm run test:cov
# Watch mode
npm run test:watchFrontend Development ​
Next.js 14 App Router ​
Minepanel uses the new App Router:
app/
├── layout.tsx # Root layout
├── page.tsx # Home page (/)
└── dashboard/
├── layout.tsx # Dashboard layout
├── page.tsx # Dashboard home (/dashboard)
└── [server]/
└── page.tsx # Server details (/dashboard/:server)Creating a New Page ​
// app/dashboard/settings/page.tsx
export default function SettingsPage() {
return (
<div>
<h1>Settings</h1>
{/* Content */}
</div>
);
}Creating a Component ​
// components/molecules/ServerCard.tsx
interface ServerCardProps {
server: Server;
}
export function ServerCard({ server }: ServerCardProps) {
return (
<div className="rounded-lg border bg-card p-4">
<h3 className="font-bold">{server.name}</h3>
<p className="text-sm text-muted-foreground">{server.type}</p>
</div>
);
}Creating a Custom Hook ​
// lib/hooks/useServerStatus.tsx
import { useQuery } from "@tanstack/react-query";
export function useServerStatus(serverId: string) {
return useQuery({
queryKey: ["server-status", serverId],
queryFn: () => fetchServerStatus(serverId),
refetchInterval: 5000, // Refetch every 5 seconds
});
}API Client ​
// services/axios.service.ts
import axios from "axios";
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_BACKEND_URL,
withCredentials: true,
});
export default api;// services/server.service.ts
import api from "./axios.service";
export async function fetchServers() {
const { data } = await api.get("/api/servers");
return data;
}
export async function createServer(config: ServerConfig) {
const { data } = await api.post("/api/servers", config);
return data;
}Docker Development ​
Building Images ​
Development Build:
docker build -t minepanel:dev .Multi-architecture Build:
# Create builder
docker buildx create --name multiplatform --use --bootstrap
# Build for multiple platforms
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ketbom/minepanel:latest \
--push .Dockerfile Structure ​
# Stage 1: Backend build
FROM node:18-alpine AS backend-builder
WORKDIR /app/backend
COPY backend/package*.json ./
RUN npm ci
COPY backend/ ./
RUN npm run build
# Stage 2: Frontend build
FROM node:18-alpine AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build
# Stage 3: Production
FROM node:18-alpine
WORKDIR /app
# Copy built files
COPY --from=backend-builder /app/backend/dist ./backend/dist
COPY --from=frontend-builder /app/frontend/.next ./frontend/.next
# Install production dependencies
RUN npm ci --production
EXPOSE 3000 8091
CMD ["npm", "start"]Debugging ​
VS Code Configuration ​
Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Backend",
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "start:debug"],
"cwd": "${workspaceFolder}/backend",
"console": "integratedTerminal"
},
{
"name": "Debug Frontend",
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"cwd": "${workspaceFolder}/frontend",
"console": "integratedTerminal"
}
]
}Browser DevTools ​
Use React Developer Tools and Redux DevTools:
# Chrome extensions
- React Developer Tools
- Redux DevTools (if using Redux)Docker Logs ​
# View logs
docker compose logs -f
# Specific service
docker compose logs -f minepanel
# Last 100 lines
docker compose logs --tail=100 minepanelTesting ​
Backend Testing ​
Unit Tests:
// server-management.service.spec.ts
describe("ServerManagementService", () => {
let service: ServerManagementService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ServerManagementService],
}).compile();
service = module.get<ServerManagementService>(ServerManagementService);
});
it("should be defined", () => {
expect(service).toBeDefined();
});
it("should list servers", async () => {
const servers = await service.listServers();
expect(Array.isArray(servers)).toBe(true);
});
});E2E Tests:
// app.e2e-spec.ts
describe("AppController (e2e)", () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it("/api/servers (GET)", () => {
return request(app.getHttpServer())
.get("/api/servers")
.expect(200)
.expect("Content-Type", /json/);
});
});Frontend Testing (Coming Soon) ​
// ServerCard.test.tsx
import { render, screen } from "@testing-library/react";
import { ServerCard } from "./ServerCard";
describe("ServerCard", () => {
it("renders server name", () => {
const server = { id: "1", name: "Test Server", type: "PAPER" };
render(<ServerCard server={server} />);
expect(screen.getByText("Test Server")).toBeInTheDocument();
});
});Contributing Guidelines ​
Before You Start ​
- Check existing issues - Maybe someone is already working on it
- Create an issue - Discuss your idea before coding
- Follow conventions - Use our coding standards
- Write tests - Add tests for new features
- Update docs - Document your changes
Pull Request Process ​
- Fork the repository
- Create a feature branchbash
git checkout -b feature/amazing-feature - Make your changes
- Commit with conventional commitsbash
git commit -m "feat: add amazing feature" - Push to your forkbash
git push origin feature/amazing-feature - Create a Pull Request
- Wait for review
Commit Convention ​
We follow Conventional Commits:
type(scope): subject
body
footerTypes:
feat: New featurefix: Bug fixdocs: Documentationstyle: Formattingrefactor: Code refactoringtest: Adding testschore: Maintenance
Examples:
feat(backend): add server backup endpoint
fix(frontend): resolve memory leak in logs component
docs(readme): update installation instructions
refactor(backend): simplify Docker integrationCode Review ​
All PRs must pass:
- âś… CI/CD tests
- âś… Code review by maintainer
- âś… No merge conflicts
- âś… Follows coding standards
Common Development Tasks ​
Add a New Server Type ​
- Update backend type definition
- Add UI option in frontend
- Configure Docker Compose template
- Test server creation
- Update documentation
Add a New Language ​
- Create translation file:
frontend/src/lib/translations/fr.ts - Add language to index:
frontend/src/lib/translations/index.ts - Update language switcher component
- Test all translations
- Update documentation
Add a New API Endpoint ​
- Create/update controller method
- Implement service logic
- Add DTOs for validation
- Write unit tests
- Update API documentation
Add a New Feature ​
- Create issue on GitHub
- Discuss approach
- Implement backend (if needed)
- Implement frontend (if needed)
- Add tests
- Update documentation
- Create pull request
Troubleshooting Development Issues ​
Port Already in Use ​
# Find process using port
lsof -i :3000
kill -9 <PID>
# Or use different ports
cd frontend
PORT=3001 npm run devDocker Permission Denied ​
# Add user to docker group
sudo usermod -aG docker $USER
# Log out and back inModule Not Found ​
# Clean install
rm -rf node_modules package-lock.json
npm installFrontend Build Errors ​
# Clear Next.js cache
cd frontend
rm -rf .next
npm run buildBackend Won't Start ​
# Check environment variables
cd backend
cat .env
# Check if port is available
lsof -i :8091Resources ​
Official Documentation ​
Useful Links ​
Community ​
- GitHub Issues: Bug reports and feature requests
- GitHub Discussions: Questions and ideas
- Pull Requests: Code contributions
Next Steps ​
- 🏗️ Understand Architecture
- đź“– Explore Features
- âť“ Read FAQ
