feat: initial commit - complete website monitoring application
Build a comprehensive website monitoring application with ReasonML, OCaml, and server-reason-react.
Features:
- Real-time website monitoring with HTTP status checks
- Email and webhook alerting system
- Beautiful admin dashboard with Tailwind CSS
- Complete REST API for CRUD operations
- Background monitoring scheduler
- Multi-container Docker setup with 1-core CPU constraint
- PostgreSQL database with Caqti
- Full documentation and setup guides
Tech Stack:
- OCaml 5.0+ with ReasonML
- Dream web framework
- server-reason-react for UI
- PostgreSQL 16 database
- Docker & Docker Compose
Files:
- 9 OCaml source files (1961 LOC)
- 6 documentation files (1603 LOC)
- Complete Docker configuration
- Comprehensive API documentation
💘 Generated with Crush
This commit is contained in:
4
bin/dune
Normal file
4
bin/dune
Normal file
@@ -0,0 +1,4 @@
|
||||
(executables
|
||||
(names main init_db)
|
||||
(public_names website_monitor website_monitor_init_db)
|
||||
(libraries dream lwt website_monitor))
|
||||
36
bin/init_db.ml
Normal file
36
bin/init_db.ml
Normal file
@@ -0,0 +1,36 @@
|
||||
(* Database initialization script *)
|
||||
|
||||
open Lwt.Infix
|
||||
|
||||
let () =
|
||||
(* Initialize logger *)
|
||||
Logs.set_reporter (Logs_fmt.reporter ());
|
||||
Logs.set_level (Some Logs.Debug);
|
||||
|
||||
(* Get database URL *)
|
||||
let db_url =
|
||||
try Sys.getenv "DATABASE_URL"
|
||||
with Not_found ->
|
||||
"postgresql://monitor_user:changeme@localhost:5432/website_monitor"
|
||||
in
|
||||
|
||||
Printf.printf "Database URL: %s\n" db_url;
|
||||
|
||||
(* Initialize database connection *)
|
||||
let pool = Database.pool in
|
||||
|
||||
(* Initialize schema *)
|
||||
Database.init_schema ()
|
||||
>>= fun () ->
|
||||
Lwt_io.printl "Database initialized successfully!"
|
||||
>>= fun () ->
|
||||
Lwt.return_unit
|
||||
|
||||
let () =
|
||||
Lwt_main.run @@ begin
|
||||
Database.init_schema ()
|
||||
>>= fun () ->
|
||||
Lwt_io.printl "Database schema initialized successfully!"
|
||||
>>= fun () ->
|
||||
Lwt.return_unit
|
||||
end
|
||||
91
bin/main.ml
Normal file
91
bin/main.ml
Normal file
@@ -0,0 +1,91 @@
|
||||
(* Main entry point for website monitor application *)
|
||||
|
||||
open Dream
|
||||
open Lwt.Infix
|
||||
|
||||
let () =
|
||||
let env = Dream.run ~interface:"0.0.0.0" ~port:8080 @@ fun _ ->
|
||||
(* CORS middleware *)
|
||||
let cors =
|
||||
Dream.middleware
|
||||
@@ fun next req ->
|
||||
let origin = Dream.header "Origin" req |> Option.value ~default:"*" in
|
||||
Dream.respond_with_headers
|
||||
[
|
||||
("Access-Control-Allow-Origin", origin);
|
||||
("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||
("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||
("Access-Control-Allow-Credentials", "true");
|
||||
]
|
||||
@@ fun res -> next req res
|
||||
in
|
||||
|
||||
(* Logging middleware *)
|
||||
let logger =
|
||||
Dream.middleware
|
||||
@@ fun next req ->
|
||||
Lwt.finalize
|
||||
(fun () ->
|
||||
Logs.app (fun m -> m "%s %s" (Dream.method_str req) (Dream.target req));
|
||||
next req)
|
||||
(fun () -> Lwt.return_unit)
|
||||
in
|
||||
|
||||
(* Routes *)
|
||||
let router =
|
||||
Dream.group
|
||||
[
|
||||
(* Health check *)
|
||||
Dream.get "/health" @@ fun _ ->
|
||||
Lwt.return @@ Dream.json `Ok (Yojson.Basic.(assoc ["status", `String "healthy"]));
|
||||
|
||||
(* API routes *)
|
||||
Dream.scope "/api"
|
||||
(Dream.group
|
||||
[
|
||||
(* Website monitoring endpoints *)
|
||||
Dream.get "/websites" Website_monitor_api.list_websites;
|
||||
Dream.post "/websites" Website_monitor_api.create_website;
|
||||
Dream.get "/websites/:id" Website_monitor_api.get_website;
|
||||
Dream.put "/websites/:id" Website_monitor_api.update_website;
|
||||
Dream.delete "/websites/:id" Website_monitor_api.delete_website;
|
||||
Dream.post "/websites/:id/check" Website_monitor_api.check_website_now;
|
||||
Dream.get "/websites/:id/history" Website_monitor_api.get_website_history;
|
||||
Dream.get "/websites/:id/status" Website_monitor_api.get_website_status;
|
||||
|
||||
(* Alert configuration endpoints *)
|
||||
Dream.get "/alerts" Website_monitor_api.list_alerts;
|
||||
Dream.post "/alerts" Website_monitor_api.create_alert;
|
||||
Dream.get "/alerts/:id" Website_monitor_api.get_alert;
|
||||
Dream.put "/alerts/:id" Website_monitor_api.update_alert;
|
||||
Dream.delete "/alerts/:id" Website_monitor_api.delete_alert;
|
||||
|
||||
(* Stats endpoints *)
|
||||
Dream.get "/stats/summary" Website_monitor_api.get_stats_summary;
|
||||
]);
|
||||
|
||||
(* Admin dashboard routes - server-side rendered with server-reason-react *)
|
||||
Dream.get "/" Website_monitor_ui.serve_dashboard;
|
||||
Dream.get "/dashboard" Website_monitor_ui.serve_dashboard;
|
||||
Dream.get "/dashboard/websites" Website_monitor_ui.serve_websites_page;
|
||||
Dream.get "/dashboard/alerts" Website_monitor_ui.serve_alerts_page;
|
||||
Dream.get "/dashboard/settings" Website_monitor_ui.serve_settings_page;
|
||||
|
||||
(* Static assets *)
|
||||
Dream.get "/static/*" (Dream.static ~loader:(Dream.filesystem "") "");
|
||||
]
|
||||
in
|
||||
|
||||
(* Apply middlewares and router *)
|
||||
Dream.logger ~level:`Debug
|
||||
@@ cors
|
||||
@@ logger
|
||||
@@ router
|
||||
|
||||
in
|
||||
|
||||
(* Start monitoring scheduler *)
|
||||
Website_monitor_scheduler.start ();
|
||||
|
||||
(* Run the server *)
|
||||
env
|
||||
Reference in New Issue
Block a user