Skip to content

Latest commit

 

History

24 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🌴 Offly — Time Off Manager

Modern absence & time off management. Simple, fast, self-hosted.

Release License Go Test Protobuf Lint Docker Pulls Docker Image Size Go Version Node Version Vite GitHub Stars GitHub Issues


📖 New here? Check out the Quick Start Guide to get up and running in 5 minutes!

✨ Features

Feature Description
📅 Half-day grid Two-week team planner. One click on a cell posts an absence: full day → morning → afternoon → free
📉 Coverage threshold Per half-day team coverage, always visible. Below 50% the interface flags it — it never blocks
🌍 Per-country holidays A holiday follows the person's country, not the company calendar. Hatched, not clickable, and out of the coverage denominator
👥 Teams & people Team coverage cards, people table with team, job profile, country and next absence
🎪 Team events Conferences, team meals, game lunches — a dedicated screen plus a band above the planner. Any signed-in person can add one
🏷️ Job profiles Filter the planner and the people table by profile; the filter only offers profiles actually in use
📤 CSV export Absences over any date range, scoped to everyone, a team or one person
📥 CSV import Public holidays, with a row-by-row preview before anything is written (JSON also accepted)
🤖 MCP server Optional read-only Model Context Protocol endpoint for LLM agents
🔐 SSO / OIDC Optional SSO authentication via Dex (PKCE flow)
🛡️ RBAC Role-based access control (admin / user)
🚀 Self-hosted Single Docker image — no external services required

📸 Screenshots

Planner — half-day grid & coverage Teams — coverage of the day
Planner Teams
People — profiles & next absence Holidays — per country, import / export
People Holidays

🏗️ Architecture

┌─────────────────────────────────────────────────────────┐
│                     Browser (React)                      │
│      TypeScript · design tokens (CSS) · Vite 8          │
└───────────────────────┬─────────────────────────────────┘
                        │ HTTP / REST          MCP (opt-in)
┌───────────────────────▼─────────────────────────────────┐
│                    Go Backend  :8080                     │
│   /api/  gRPC-Gateway → gRPC :50051 (loopback)          │
│   /mcp   Model Context Protocol      /docs  Swagger UI  │
├─────────────────┬───────────────────────────────────────┤
│   SQLite (default)    │   MongoDB (optional)            │
└───────────────────────┴─────────────────────────────────┘

The single Docker image embeds both the Go binary and the compiled React SPA. The backend serves the frontend static files and provides the REST/gRPC API.

Every REST call is a real gRPC call: the gateway dials the in-process gRPC server on loopback.

The interface follows design.md — tokens, tone, component inventory and the product's core rule (you declare a portion of a day, never a leave type). Read it before changing anything visual.

🛠️ Tech Stack

Layer Technology
Backend Go 1.26, gRPC, gRPC-Gateway, Protocol Buffers, MCP Go SDK
Frontend React 18, TypeScript 5, Vite 8, CSS design tokens
Typography IBM Plex Sans / IBM Plex Mono
Database SQLite (default) · MongoDB (optional)
Auth Dex (OIDC/PKCE), JWT, JWKS
Container Docker (multi-stage, Alpine)
Orchestration Kubernetes · Helm · Skaffold
CI/CD GitHub Actions (SHA-pinned)

📋 Prerequisites

Tool Version Purpose
Go 1.26+ Backend
Node.js 24+ Frontend
Task latest Task runner
Buf latest Protobuf tooling
Docker latest Containers

🚀 Quick Start

Option 1 — Docker Compose (recommended)

docker-compose up -d

The application is available at http://localhost:8080

Option 2 — Local development

# Clone
git clone https://github.com/BananaOps/offly.git && cd offly

# Install all dependencies and generate protobuf code
task setup

# Start the app (backend + frontend with hot reload)
task dev
Service URL
Web UI http://localhost:3000
REST API http://localhost:8080/api/v1
Swagger UI http://localhost:8080/docs
gRPC localhost:50051

Seed test data

# Requires k6 — see k6-README.md
k6 run k6-seed-data.js

Creates 5 departments, 10 teams, 12 users, 29 public holidays, and random absences.

🐳 Docker

# Pull & run (in-memory storage — no DB required)
docker run -p 8080:8080 bananaops/offly:latest

# With persistent SQLite
docker run -p 8080:8080 \
  -v $(pwd)/data:/app/data \
  bananaops/offly:latest

# With MongoDB
docker run -p 8080:8080 \
  -e STORAGE_TYPE=mongodb \
  -e MONGO_URI=mongodb://host.docker.internal:27017 \
  bananaops/offly:latest

See DOCKER.md for the full deployment guide.

☸️ Kubernetes (Helm)

# Add the chart repository
helm repo add offly https://bananaops.github.io/offly
helm repo update

# Install
helm install offly offly/offly

# Install a specific version
helm install offly offly/offly --version 0.1.0

# Upgrade
helm upgrade offly offly/offly

⚙️ Environment Variables

Variable Description Default
STORAGE_TYPE sqlite or mongodb sqlite
SQLITE_DB_PATH SQLite database path /app/data/offly.db
MONGO_URI MongoDB connection string mongodb://localhost:27017
HTTP_PORT HTTP server port 8080
GRPC_PORT gRPC server port 50051
AUTH_ENABLED Enable SSO authentication false
AUTH_ISSUER_URL OIDC issuer URL —
AUTH_CLIENT_ID OIDC client ID —
AUTH_CLIENT_SECRET OIDC client secret, used on the callback exchange —
AUTH_JWKS_URL JWKS endpoint <issuer>/keys
AUTH_JWKS_CACHE_TTL JWKS cache lifetime, in seconds 3600
AUTH_ADMIN_EMAILS Comma-separated emails granted the admin role (ADMIN_EMAILS also read) —
MCP_ENABLED Expose the read-only MCP server at /mcp false

HTTP_PORT and GRPC_PORT are read at startup; an unparseable value logs a warning and falls back to the default. gRPC always binds to loopback — it is reached only by the in-process gateway.

🔐 SSO Authentication

Offly supports optional SSO via Dex (OIDC/PKCE flow).

Browser ──PKCE──▶ Dex ──ID Token──▶ Backend ──JWT verify──▶ SQLite
Role Permissions
admin Full access — users, teams, holidays, absences
user Read all · Edit own profile & absences only · Add and edit events

See SSO-README.md for the full configuration guide.

🤖 MCP Server

Offly can expose its data to LLM agents through the Model Context Protocol. Set MCP_ENABLED=true and the server serves the streamable HTTP transport at /mcp, on the same port as the REST API — no extra binary, no extra port.

Tool Description
list_users Every user with team, country and job profile
list_teams Teams, optionally filtered by department, with member counts
list_absences Absences overlapping a YYYY-MM-DD date range
list_holidays Public holidays, optionally filtered by country and year
list_events Team events (conferences, meals, game lunches), optionally within a date range
team_presence Who is present/away in a team on a day, accounting for absences and each member's public holidays

Register it with Claude Code:

claude mcp add --transport http offly http://localhost:8080/mcp

⚠️ The endpoint is unauthenticated and must not be exposed publicly. It bypasses the AUTH_ENABLED / RBAC layer entirely — anyone who can reach /mcp can read every absence. Keep it on an internal network or behind an authenticating proxy.

All tools are read-only. Writes are deliberately not exposed: the "users may only modify their own absences" rules live in the HTTP middleware and are not reachable from the MCP handlers, so a write tool would let any caller act on behalf of anyone.

🔌 API

REST (port 8080)

Interactive documentation available at http://localhost:8080/docs

Method Endpoint Description
GET /api/v1/health Health check
GET/POST /api/v1/users List / create users
GET/PUT/DELETE /api/v1/users/{id} Get / update / delete user
GET/POST /api/v1/teams List / create teams
GET/POST /api/v1/absences List / create absences
PUT/DELETE /api/v1/absences/{id} Update / delete absence
GET/POST /api/v1/holidays List / create public holidays
GET/POST /api/v1/events List / create team events
PUT/DELETE /api/v1/events/{id} Update / delete event
GET /api/v1/auth/config SSO configuration
POST /api/v1/auth/ensure-user Auto-provision SSO user

gRPC (port 50051)

Services: AbsenceService · UserService · OrganizationService · HolidayService · EventService

🧰 Task Commands

task setup            # Install deps + generate protobuf code
task dev              # Start backend + frontend (hot reload, SQLite — no external service)
task build            # Build the full application
task test             # Run all tests
task lint             # Lint backend + frontend
task format           # Format backend + frontend
task proto            # Regenerate protobuf code
task proto:lint       # Lint protobuf definitions
task mongo:start      # Start MongoDB in Docker
task mongo:stop       # Stop MongoDB
task pre-commit       # Format + lint + test (run before committing)
task clean            # Remove generated files

📁 Project Structure

offly/
├── backend/
│   ├── cmd/server/          # Server entry point
│   ├── internal/
│   │   ├── auth/            # OIDC, JWT, RBAC middleware
│   │   ├── mcp/             # Read-only MCP server (opt-in)
│   │   ├── service/         # gRPC service implementations
│   │   └── storage/         # SQLite + MongoDB adapters
│   └── proto/               # Protocol Buffer definitions
├── frontend/
│   └── src/
│       ├── components/offly/  # The interface (see design.md)
│       │   ├── OfflyApp       # Shell: data + screen switching
│       │   ├── Rail           # Navigation
│       │   ├── CalendarScreen # Half-day grid, coverage, entry
│       │   ├── TeamsScreen · PeopleScreen · HolidaysScreen
│       │   ├── ExportMenu     # CSV export of absences
│       │   └── HolidayTransfer# Holiday import / export
│       ├── design/offly.css   # Design tokens
│       ├── lib/
│       │   ├── halfday.ts     # am|pm|full model, coverage
│       │   ├── csv.ts         # CSV read / write
│       │   └── profiles.ts    # Job profile labels
│       ├── api.ts             # REST API client
│       ├── auth.ts            # PKCE / JWT helpers
│       └── types.ts           # TypeScript types
├── design.md                # Design system — read before any visual change
├── helm/offly/              # Helm chart for Kubernetes
├── dex/                     # Dex OIDC provider (dev/test)
├── Dockerfile               # Multi-stage build (frontend + backend)
├── docker-compose.yml       # Local stack
├── Taskfile.yml             # Task automation
└── k6-seed-data.js          # Test data generator

🤝 Contributing

Contributions are welcome! We follow Conventional Commits:

git commit -m "feat: add new absence type filter"
git commit -m "fix: correct date calculation in calendar"
git commit -m "docs: update API reference"

Types: feat · fix · docs · chore · ci · refactor · perf · test

Releases are automated via Release Please.

🐛 Troubleshooting

MongoDB won't start
task mongo:stop && task mongo:start
Protobuf compilation errors
task install:backend
task proto:lint   # Check for syntax errors
task proto        # Regenerate
Frontend dependency issues
cd frontend && rm -rf node_modules package-lock.json && npm install
SSO / Dex issues

See SSO-README.md for detailed troubleshooting steps.

📄 License

Released under the MIT License — © BananaOps


Documentation · Issues · Discussions · Docker Hub

Made with ❤️ by BananaOps

About

Time Off manager

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages