chore: upgrade to Elixir 1.19.5 / OTP 28, add Gleam to dev shell
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
35f29f42e3
commit
afcbba3fc7
7 changed files with 80894 additions and 47260 deletions
13
apps/centralcloud_my/lib/centralcloud_my/application.ex
Normal file
13
apps/centralcloud_my/lib/centralcloud_my/application.ex
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
defmodule CentralcloudMy.Application do
|
||||
use Application
|
||||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
CentralcloudMy.Endpoint
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_one, name: CentralcloudMy.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
defmodule CentralcloudMy.SessionController do
|
||||
use Phoenix.Controller, namespace: CentralcloudMy
|
||||
import Plug.Conn
|
||||
|
||||
def new(conn, _params) do
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_resp(200, login_page(conn))
|
||||
end
|
||||
|
||||
def create(conn, %{"username" => _username, "password" => _password}) do
|
||||
# TODO: validate against DR portal customer_users or Authentik
|
||||
conn
|
||||
|> put_flash(:error, "Invalid credentials")
|
||||
|> redirect(to: "/login")
|
||||
end
|
||||
|
||||
def delete(conn, _params) do
|
||||
conn
|
||||
|> clear_session()
|
||||
|> redirect(to: "/login")
|
||||
end
|
||||
|
||||
def oidc_callback(conn, params) do
|
||||
# TODO: exchange code with Authentik, set session
|
||||
_ = params
|
||||
conn
|
||||
|> put_flash(:info, "SSO login coming soon")
|
||||
|> redirect(to: "/login")
|
||||
end
|
||||
|
||||
defp login_page(conn) do
|
||||
csrf = Phoenix.Controller.get_csrf_token()
|
||||
flash_error = Phoenix.Flash.get(conn.assigns[:flash] || %{}, :error)
|
||||
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="csrf-token" content="#{csrf}"/>
|
||||
<title>Sign in — CentralCloud</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: #0f172a; color: #e2e8f0; display: flex;
|
||||
align-items: center; justify-content: center; min-height: 100vh; }
|
||||
.card { background: #1e293b; border: 1px solid #334155; border-radius: 12px;
|
||||
padding: 2.5rem; width: 100%; max-width: 380px; }
|
||||
h1 { font-size: 1.4rem; font-weight: 700; color: #38bdf8; margin-bottom: 0.25rem; }
|
||||
p.sub { color: #94a3b8; font-size: 0.85rem; margin-bottom: 2rem; }
|
||||
label { display: block; font-size: 0.8rem; color: #94a3b8; margin-bottom: 0.3rem; }
|
||||
input { width: 100%; padding: 0.6rem 0.8rem; background: #0f172a; border: 1px solid #475569;
|
||||
border-radius: 6px; color: #e2e8f0; font-size: 0.9rem; margin-bottom: 1rem; }
|
||||
button { width: 100%; padding: 0.7rem; background: #0ea5e9; border: none;
|
||||
border-radius: 6px; color: #fff; font-weight: 600; cursor: pointer; font-size: 0.95rem; }
|
||||
button:hover { background: #38bdf8; }
|
||||
.divider { text-align: center; color: #475569; font-size: 0.8rem; margin: 1rem 0; }
|
||||
.sso { width: 100%; padding: 0.7rem; background: #1e293b; border: 1px solid #475569;
|
||||
border-radius: 6px; color: #e2e8f0; font-size: 0.9rem; cursor: pointer; text-align: center;
|
||||
text-decoration: none; display: block; margin-top: 0.5rem; }
|
||||
.sso:hover { border-color: #38bdf8; }
|
||||
.error { background: #7f1d1d; border: 1px solid #dc2626; border-radius: 6px;
|
||||
padding: 0.6rem 0.8rem; font-size: 0.85rem; margin-bottom: 1rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>CentralCloud</h1>
|
||||
<p class="sub">Sign in to your account</p>
|
||||
#{if flash_error, do: "<div class='error'>#{flash_error}</div>", else: ""}
|
||||
<form method="post" action="/login">
|
||||
<input type="hidden" name="_csrf_token" value="#{csrf}"/>
|
||||
<label>Username</label>
|
||||
<input type="text" name="username" autocomplete="username" required/>
|
||||
<label>Password</label>
|
||||
<input type="password" name="password" autocomplete="current-password" required/>
|
||||
<button type="submit">Sign in</button>
|
||||
</form>
|
||||
<div class="divider">or</div>
|
||||
<a class="sso" href="/auth/callback">🔐 Sign in with CentralCloud SSO</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
end
|
||||
end
|
||||
28
apps/centralcloud_my/lib/centralcloud_my/endpoint.ex
Normal file
28
apps/centralcloud_my/lib/centralcloud_my/endpoint.ex
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
defmodule CentralcloudMy.Endpoint do
|
||||
use Phoenix.Endpoint, otp_app: :centralcloud_my
|
||||
|
||||
socket "/live", Phoenix.LiveView.Socket,
|
||||
websocket: [connect_info: [session: {CentralcloudMy.Router, :session_opts, []}]]
|
||||
|
||||
plug Plug.Static,
|
||||
at: "/",
|
||||
from: {:centralcloud_my, "priv/static"},
|
||||
gzip: false
|
||||
|
||||
plug Plug.RequestId
|
||||
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
|
||||
|
||||
plug Plug.Parsers,
|
||||
parsers: [:urlencoded, :multipart, :json],
|
||||
pass: ["*/*"],
|
||||
json_decoder: Phoenix.json_library()
|
||||
|
||||
plug Plug.MethodOverride
|
||||
plug Plug.Head
|
||||
plug Plug.Session, store: :cookie,
|
||||
key: "_centralcloud_my_session",
|
||||
signing_salt: "my_salt_2026",
|
||||
same_site: "Lax"
|
||||
|
||||
plug CentralcloudMy.Router
|
||||
end
|
||||
55
apps/centralcloud_my/lib/centralcloud_my/layouts.ex
Normal file
55
apps/centralcloud_my/lib/centralcloud_my/layouts.ex
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
defmodule CentralcloudMy.Layouts do
|
||||
use Phoenix.Component
|
||||
|
||||
def render("root.html", assigns) do
|
||||
~H"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="csrf-token" content={Phoenix.Controller.get_csrf_token()}/>
|
||||
<title>CentralCloud</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: #0f172a; color: #e2e8f0; min-height: 100vh; }
|
||||
nav { background: #1e293b; padding: 1rem 2rem; display: flex;
|
||||
align-items: center; justify-content: space-between; border-bottom: 1px solid #334155; }
|
||||
nav .brand { font-weight: 700; font-size: 1.1rem; color: #38bdf8; }
|
||||
nav a { color: #94a3b8; text-decoration: none; margin-left: 1.5rem; font-size: 0.9rem; }
|
||||
nav a:hover { color: #e2e8f0; }
|
||||
main { padding: 2rem; max-width: 1200px; margin: 0 auto; }
|
||||
.flash { padding: 0.75rem 1rem; border-radius: 6px; margin-bottom: 1rem; }
|
||||
.flash-info { background: #1d4ed8; }
|
||||
.flash-error { background: #dc2626; }
|
||||
</style>
|
||||
{live_title_tag(assigns[:page_title] || "My CentralCloud")}
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<span class="brand">⚡ CentralCloud</span>
|
||||
<div>
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/dr">DR Status</a>
|
||||
<a href="/billing">Billing</a>
|
||||
<a href="/support">Support</a>
|
||||
<a href="/logout" data-method="delete">Sign out</a>
|
||||
</div>
|
||||
</nav>
|
||||
<main>
|
||||
<p :if={msg = Phoenix.Flash.get(@flash, :info)} class="flash flash-info">{msg}</p>
|
||||
<p :if={msg = Phoenix.Flash.get(@flash, :error)} class="flash flash-error">{msg}</p>
|
||||
{@inner_content}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
end
|
||||
|
||||
def render("app.html", assigns) do
|
||||
~H"""
|
||||
{@inner_content}
|
||||
"""
|
||||
end
|
||||
end
|
||||
40
apps/centralcloud_my/lib/centralcloud_my/router.ex
Normal file
40
apps/centralcloud_my/lib/centralcloud_my/router.ex
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
defmodule CentralcloudMy.Router do
|
||||
use Phoenix.Router
|
||||
import Phoenix.LiveView.Router
|
||||
|
||||
@session_opts [store: :cookie, key: "_centralcloud_my_session", signing_salt: "my_salt_2026"]
|
||||
def session_opts, do: @session_opts
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
plug :fetch_session
|
||||
plug :fetch_live_flash
|
||||
plug :put_root_layout, html: {CentralcloudMy.Layouts, :root}
|
||||
plug :protect_from_forgery
|
||||
plug :put_secure_browser_headers
|
||||
end
|
||||
|
||||
pipeline :auth do
|
||||
plug CentralcloudMy.Plugs.RequireAuth
|
||||
end
|
||||
|
||||
# Public routes
|
||||
scope "/", CentralcloudMy do
|
||||
pipe_through :browser
|
||||
|
||||
get "/login", SessionController, :new
|
||||
post "/login", SessionController, :create
|
||||
delete "/logout", SessionController, :delete
|
||||
get "/auth/callback", SessionController, :oidc_callback
|
||||
end
|
||||
|
||||
# Authenticated customer routes
|
||||
scope "/", CentralcloudMy do
|
||||
pipe_through [:browser, :auth]
|
||||
|
||||
live "/", DashboardLive, :index
|
||||
live "/dr", ReplicationLive, :index
|
||||
live "/billing", BillingLive, :index
|
||||
live "/support", SupportLive, :index
|
||||
end
|
||||
end
|
||||
127925
erl_crash.dump
127925
erl_crash.dump
File diff suppressed because one or more lines are too long
|
|
@ -13,8 +13,9 @@
|
|||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
elixir
|
||||
erlang
|
||||
elixir_1_19 # 1.19.5 — latest stable; upgrade to 1.20 when nixpkgs picks it up
|
||||
erlang_28 # OTP 28.5
|
||||
gleam # ready when we want type-safe core modules
|
||||
nodejs_22 # for Phoenix asset pipeline
|
||||
inotify-tools # LiveReload on Linux
|
||||
];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue