Changes
Build and Push Docker Images / build (push) Successful in 31s

This commit is contained in:
2026-05-20 14:25:07 +03:00
parent fb5b254570
commit aa79b422e4
12 changed files with 239 additions and 1 deletions
+8 -1
View File
@@ -1,10 +1,17 @@
# .env.template - Template of environment variables for docker-compose
# Copy this file to `.env.production` or `.env.staging` and fill the secret values.
# Copy this file to `.env` (local), `.env.staging`, or `.env.production` and fill the secret values.
# Do NOT commit your `.env.*` files containing real secrets.
# Common
ASPNETCORE_ENVIRONMENT=Development
# Web (myai-web container) - maps to web appsettings Site:Mode section
# Controls the public site experience:
# Normal - full site (default)
# UnderConstruction - redirect visitors to /under-construction.html
# Unavailable - redirect visitors to /site-unavailable.html (HTTP 503)
Site__Mode=Normal
# API (main)
ASPNETCORE_URLS=http://+:8080
APP_ENVIRONMENT_NAME=myai-Development
@@ -260,6 +260,9 @@ services:
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Production}
- ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080}
- APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.production}
# Site: matches web appsettings Site section (Normal, UnderConstruction, Unavailable)
- Site__Mode=${Site__Mode:-Normal}
networks:
- myai-network
restart: unless-stopped
@@ -260,6 +260,9 @@ services:
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Staging}
- ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080}
- APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.staging}
# Site: matches web appsettings Site section (Normal, UnderConstruction, Unavailable)
- Site__Mode=${Site__Mode:-Normal}
networks:
- myai-network
restart: unless-stopped
+3
View File
@@ -289,6 +289,9 @@ services:
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Development}
- ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080}
- APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.local}
# Site: matches web appsettings Site section (Normal, UnderConstruction, Unavailable)
- Site__Mode=${Site__Mode:-Normal}
networks:
- myai-network
restart: unless-stopped
+72
View File
@@ -0,0 +1,72 @@
using Microsoft.Extensions.Options;
using Web.Settings;
namespace Web.Middleware;
public sealed class SiteModeMiddleware(RequestDelegate next, IOptions<SiteSettings> options)
{
private static readonly string[] StaticAssetPrefixes =
[
"/css/",
"/js/",
"/img/",
"/logo/",
"/fonts/"
];
public async Task InvokeAsync(HttpContext context)
{
var mode = ResolveMode(options.Value.Mode);
if (mode == SiteModes.Normal)
{
await next(context);
return;
}
var path = context.Request.Path.Value ?? "/";
var statusPath = GetStatusPath(mode);
if (IsAllowedPath(path, statusPath))
{
if (mode == SiteModes.Unavailable &&
path.Equals("/site-unavailable.html", StringComparison.OrdinalIgnoreCase))
{
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
context.Response.Headers.RetryAfter = "3600";
}
await next(context);
return;
}
context.Response.Redirect(statusPath);
}
private static string ResolveMode(string? configuredMode)
{
if (string.Equals(configuredMode, SiteModes.UnderConstruction, StringComparison.OrdinalIgnoreCase))
return SiteModes.UnderConstruction;
if (string.Equals(configuredMode, SiteModes.Unavailable, StringComparison.OrdinalIgnoreCase))
return SiteModes.Unavailable;
return SiteModes.Normal;
}
private static string GetStatusPath(string mode) =>
mode == SiteModes.UnderConstruction
? "/under-construction.html"
: "/site-unavailable.html";
private static bool IsAllowedPath(string path, string statusPath)
{
if (path.Equals(statusPath, StringComparison.OrdinalIgnoreCase))
return true;
if (path.Equals("/favicon.ico", StringComparison.OrdinalIgnoreCase))
return true;
return StaticAssetPrefixes.Any(prefix =>
path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
}
}
+6
View File
@@ -1,13 +1,19 @@
// Program.cs
using Web.Middleware;
using Web.Settings;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRouting();
builder.Services.Configure<SiteSettings>(builder.Configuration.GetSection(SiteSettings.SectionName));
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.UseMiddleware<SiteModeMiddleware>();
// Static site
app.UseDefaultFiles();
app.UseStaticFiles();
+18
View File
@@ -0,0 +1,18 @@
namespace Web.Settings;
public sealed class SiteSettings
{
public const string SectionName = "Site";
/// <summary>
/// Controls which page visitors see: Normal, UnderConstruction, or Unavailable.
/// </summary>
public string Mode { get; set; } = SiteModes.Normal;
}
public static class SiteModes
{
public const string Normal = "Normal";
public const string UnderConstruction = "UnderConstruction";
public const string Unavailable = "Unavailable";
}
+3
View File
@@ -5,6 +5,9 @@
"Microsoft.AspNetCore": "Warning"
}
},
"Site": {
"Mode": "Normal"
},
"ReverseProxy": {
"Routes": {
"apiRoute": {
+4
View File
@@ -7,6 +7,10 @@
},
"AllowedHosts": "*",
"Site": {
"Mode": "Normal"
},
"ReverseProxy": {
"Routes": {
"apiRoute": {
+27
View File
@@ -48,6 +48,33 @@ img {
overflow: hidden
}
.status-hero {
min-height: 100vh;
display: flex;
align-items: center;
padding: 48px 0
}
.status-card {
max-width: 720px;
margin: 0 auto;
padding: 40px;
border-radius: var(--card-radius);
background: var(--panel);
border: 1px solid var(--panel-border);
box-shadow: var(--shadow)
}
.status-brand {
margin-bottom: 28px
}
.status-note {
margin: 24px 0 0;
color: var(--muted);
line-height: 1.6
}
.header {
position: sticky;
top: 0;
+46
View File
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MyAi.ro · Temporarily unavailable</title>
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#071326" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" type="image/png" href="/img/favicon-256.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/myai.css" />
</head>
<body>
<div class="site-shell">
<main>
<section class="hero status-hero">
<div class="container">
<div class="status-card">
<a class="brand status-brand" href="/" aria-label="MyAi.ro home">
<span class="brand-mark">
<img src="/img/myai-logo.svg" alt="MyAi.ro">
</span>
<span>
<span class="brand-text">MyAi.ro</span>
<small>AI engineering showcase</small>
</span>
</a>
<span class="eyebrow">Temporarily unavailable</span>
<h1>The site is not available right now.</h1>
<p class="hero-text">
MyAi.ro is temporarily offline for maintenance.
We expect to be back shortly.
</p>
<p class="status-note">
<strong>RO:</strong> Site-ul nu este disponibil temporar. Vă mulțumim pentru răbdare.
</p>
</div>
</div>
</section>
</main>
</div>
</body>
</html>
+46
View File
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MyAi.ro · Under construction</title>
<meta name="robots" content="noindex, nofollow" />
<meta name="theme-color" content="#071326" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" type="image/png" href="/img/favicon-256.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/myai.css" />
</head>
<body>
<div class="site-shell">
<main>
<section class="hero status-hero">
<div class="container">
<div class="status-card">
<a class="brand status-brand" href="/" aria-label="MyAi.ro home">
<span class="brand-mark">
<img src="/img/myai-logo.svg" alt="MyAi.ro">
</span>
<span>
<span class="brand-text">MyAi.ro</span>
<small>AI engineering showcase</small>
</span>
</a>
<span class="eyebrow">Under construction</span>
<h1>We are building something new.</h1>
<p class="hero-text">
MyAi.ro is being updated with new AI demos and improvements.
Please check back soon.
</p>
<p class="status-note">
<strong>RO:</strong> Site-ul este în construcție. Reveniți în curând.
</p>
</div>
</div>
</section>
</main>
</div>
</body>
</html>