Module Development¶
ContaraNAS uses a modular architecture where functionality is provided through independent modules. Each module is a self-contained Python package that:
- Defines its own state (data it manages)
- Provides a tile for the dashboard (how it appears to users)
- Exposes actions (things users can do)
- Optionally provides modals (detailed views and forms)
The framework handles all the complexity of:
- Rendering your UI on the frontend
- Synchronizing state between backend and frontend
- Routing user interactions back to your code
- Managing module lifecycle (enable, disable, initialize)
You write Python. The framework does the rest.
Module Types¶
Built-in Modules¶
Located in backend/ContaraNAS/modules/builtin/. These ship with ContaraNAS and are maintained by the core team.
Community Modules¶
Located in backend/ContaraNAS/modules/community/. These are installed by users and can be developed by anyone.
Directory Structure¶
A module is a directory containing at minimum:
A more complete module might look like:
my_module/
├── __init__.py # Module class
├── module.json # Metadata
├── controllers/ # Business logic
│ ├── __init__.py
│ └── main_controller.py
├── services/ # External integrations
│ ├── __init__.py
│ └── api_service.py
└── dtos/ # Data transfer objects
├── __init__.py
└── models.py
Quick Start¶
Here is a minimal working module:
# my_module/__init__.py
from ContaraNAS.core.module import Module, ModuleState
from ContaraNAS.core.action import action
class MyModule(Module):
"""A simple example module"""
class State(ModuleState):
counter: int = 0
async def initialize(self) -> None:
"""Called once when the module is first enabled"""
pass
async def start_monitoring(self) -> None:
"""Called when module is enabled (start background tasks here)"""
pass
async def stop_monitoring(self) -> None:
"""Called when module is disabled (clean up here)"""
pass
def get_tile(self) -> "Tile":
"""Return the dashboard tile UI"""
from ContaraNAS.core.ui import Tile, Stat, Button
return Tile(
icon="Counter",
title="My Module",
stats=[Stat(label="Count", value=self._typed_state.counter if self._typed_state else 0)],
actions=[Button(label="Increment", on_click=self.increment)],
)
@action
async def increment(self) -> None:
"""Increment the counter (called from frontend)"""
if self._typed_state:
self._typed_state.counter += 1
self._typed_state.commit()
// my_module/module.json
{
"name": "my_module",
"displayName": "My Module",
"version": "1.0.0",
"author": "Your Name",
"description": "A simple example module",
"icon": "wrench",
"engine": {
"contaranas": "^0.1.0"
},
"platforms": [
"linux",
"windows",
"darwin"
],
"dependencies": {}
}
Icon Names
The icon field uses Lucide icon names (e.g., "wrench", "gamepad-2", "hard-drive"). Browse available icons at lucide.dev/icons.
What to Read Next¶
- State Management — How to define and manage module state
- Declarative UI — How to build your module's interface
- Actions — How to handle user interactions
Key Concepts¶
| Concept | Description |
|---|---|
| ModuleState | A Pydantic model that holds your module's data. Changes are tracked automatically. |
| Component | A UI building block (Button, Card, Table, etc.) defined in Python, rendered in frontend. |
| Action | A method decorated with @action that can be called from the frontend. |
| Tile | The card shown on the dashboard representing your module. |
| Modal | A popup dialog for detailed views or forms. |
| get_modals() | Method returning list of Modal definitions. See Modal. |
| commit() | Signals that state changes should be pushed to the frontend. |