From eedbab6c990fd31ccc427c5ea5ca6e53f77ecd5e Mon Sep 17 00:00:00 2001 From: Rene Schwarzer Date: Thu, 9 Apr 2026 21:43:18 +0200 Subject: [PATCH 1/7] feat: begin development of version 0.0.11-alpha --- README.md | 2 +- WebExpress.spec | 2 +- {doc => docs}/development_guide.md | 0 {doc => docs}/installation_guide.md | 0 src/WebExpress/WebExpress.csproj | 6 +++--- 5 files changed, 5 insertions(+), 5 deletions(-) rename {doc => docs}/development_guide.md (100%) rename {doc => docs}/installation_guide.md (100%) diff --git a/README.md b/README.md index a4566e5..283afea 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![WebExpress-Framework](https://raw.githubusercontent.com/webexpress-framework/.github/main/docs/assets/img/banner.png) # WebExpress -WebExpress is a lightweight web server optimized for use in low-performance environments (e.g. Rasperry PI). By providing a powerful plugin system and a comprehensive API, web applications can be easily and quickly integrated into a .net language (e.g. C#). Some advantages of WebExpress are: +WebExpress is a lightweight web server optimized for use in low-performance environments (e.g., Rasperry PI). By providing a powerful plugin system and a comprehensive API, web applications can be easily and quickly integrated into a .net language (e.g., C#). Some advantages of WebExpress are: - It is easy to use. - It offers a variety of features and tools that can help you build and manage your website. diff --git a/WebExpress.spec b/WebExpress.spec index 5beb918..7b60b74 100644 --- a/WebExpress.spec +++ b/WebExpress.spec @@ -1,7 +1,7 @@ WebExpress - 0.0.10-alpha + 0.0.11-alpha WebExpress webexpress-framework@outlook.com MIT diff --git a/doc/development_guide.md b/docs/development_guide.md similarity index 100% rename from doc/development_guide.md rename to docs/development_guide.md diff --git a/doc/installation_guide.md b/docs/installation_guide.md similarity index 100% rename from doc/installation_guide.md rename to docs/installation_guide.md diff --git a/src/WebExpress/WebExpress.csproj b/src/WebExpress/WebExpress.csproj index f51ee69..8e85022 100644 --- a/src/WebExpress/WebExpress.csproj +++ b/src/WebExpress/WebExpress.csproj @@ -1,8 +1,8 @@  - 0.0.10.0 - 0.0.10.0 + 0.0.11.0 + 0.0.11.0 Exe net10.0 ufo.ico @@ -15,7 +15,7 @@ - + From 27b80e4a8e9685d2657349fc9579ecbca054babe Mon Sep 17 00:00:00 2001 From: Rene Schwarzer Date: Sun, 12 Apr 2026 14:41:03 +0200 Subject: [PATCH 2/7] feat: general improvements and minor bugs --- docs/development_guide.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/development_guide.md b/docs/development_guide.md index dd032c8..1ab00f0 100644 --- a/docs/development_guide.md +++ b/docs/development_guide.md @@ -711,6 +711,7 @@ Endpoints are (web) elements that can be accessed with a URI (Uniform Resource I ║ ¦ │ PluginContext:IPluginContext │ ¦ ║ ║ ¦ │ ApplicationContext:IApplicationContext │ ¦ ║ ║ ¦ │ Conditions:IEnumerable │ ¦ ║ +║ ¦ │ Policies:IEnumerable │ ¦ ║ ║ ¦ │ Cache:Bool │ ¦ ║ ║ ¦ │ Route:IRoute │ ¦ ║ ║ ¦ └────────────────────────────────────────┘ ¦ ║ @@ -854,6 +855,7 @@ All assets are placed under the "assets" path, which is located within the main ║ │ │ PluginContext:IPluginContext │ ¦ │ ║ ║ │ │ ApplicationContext:IApplicationContext │ ¦ │ ║ ║ │ │ Conditions:IEnumerable │ ¦ │ ║ +║ │ │ Policies:IEnumerable │ ¦ │ ║ ║ │ │ Cache:Bool │ ¦ │ ║ ║ │ │ Route:IRoute │ ¦ │ ║ ║ │ └──────────────────Δ─────────────────────┘ ¦ │ ║ @@ -882,8 +884,7 @@ Resources are typically assets that can come in various forms, such as images, v ```csharp [Segment("E")] -[Authorization(Permission.RWX, IdentityPolicyDefault.SystemAccess)] -[Authorization(Permission.R, IdentityPolicyDefault.PublicAccess)] +[Policy] public sealed class MyResource : IResource { } @@ -897,7 +898,7 @@ To provide clarity about the metadata specified in the code above, the following |SegmentInt |Parameter, String |1 |Yes |A variable path segment of type `Int`. |SegmentGuid |Parameter, String |1 |Yes |A variable path segment of type `Guid`. |IncludeSubPaths |Bool |1 |Yes |Determines whether all resources below the specified path (including segment) are processed. -|Authorization |Int, String |n |Yes |Grants authority to a policy (specifying the id) (see section notification model). +|Policy |`IIdentityPolicy` |n |Yes |Grants authority to a policy. |Condition |`ICondition` |n |Yes |Condition that must be met for the resource to be available. |Cache |- |1 |Yes |Determines whether the resource is created once and reused each time it is called. |Optional |- |1 |Yes |Marks a resource as optional. It only becomes active if the option has been activated in the application. @@ -1038,6 +1039,7 @@ The `ResourceManager` manages all resources. However, these are only accessible ║ │ │ PluginContext:IPluginContext │ ¦ │ ║ ║ │ │ ApplicationContext:IApplicationContext │ ¦ │ ║ ║ │ │ Conditions:IEnumerable │ ¦ │ ║ +║ │ │ Policies:IEnumerable │ ¦ │ ║ ║ │ │ Cache:Bool │ ¦ │ ║ ║ │ │ Route:IRoute │ ¦ │ ║ ║ │ └──────────────────Δ─────────────────────┘ ¦ │ ║ @@ -1242,6 +1244,7 @@ The following class diagram illustrates the architecture of the `IncludeManager` ║ │ │ PluginContext:IPluginContext │ ¦ │ ║ ║ │ │ ApplicationContext:IApplicationContext │ ¦ │ ║ ║ │ │ Conditions:IEnumerable │ ¦ │ ║ +║ │ │ Policies:IEnumerable │ ¦ │ ║ ║ │ │ Cache:Bool │ ¦ │ ║ ║ │ │ Route:IRoute │ ¦ │ ║ ║ │ └───────────────────Δ────────────────────┘ ¦ │ ║ @@ -1299,8 +1302,7 @@ Pages are a fundamental component of web applications, serving as the primary in ```csharp [Title("my page")] [Scope] -[Authorization(Permission.RWX, IdentityPolicyDefault.SystemAccess)] -[Authorization(Permission.R, IdentityPolicyDefault.PublicAccess)] +[Policy] public sealed class MyPage : IPage { public void Process(IRenderContext renderContext, VisualTree visualTree) @@ -1321,7 +1323,7 @@ To clearly illustrate the metadata described in the code above, the table below |SegmentGuid |Parameter, String |1 |Yes |A variable path segment of type `Guid`. |IncludeSubPaths |Bool |1 |Yes |Determines whether all resources below the specified path (including segment) are processed. |Scope |`IScope` |n |Yes |The scope of the page. -|Authorization |Int, String |n |Yes |Grants authority to a policy (specifying the id) (see section notification model). +|Policy |`IIdentityPolicy` |n |Yes |Grants authority to a policy. |Condition |`ICondition` |n |Yes |Condition that must be met for the resource to be available. |Cache |- |1 |Yes |Determines whether the resource is created once and reused each time it is called. |Domain |`IDomain` |n |Yes |Associates the page with one or more logical domains. Domains represent functional areas, modules or workspaces and can be used for routing, filtering or contextual grouping. @@ -1389,6 +1391,7 @@ Web pages are resources that are rendered in an HTML tree before delivery. The ` ║ │ │ PluginContext:IPluginContext │ ¦ │ ║ ║ │ │ ApplicationContext:IApplicationContext │ ¦ │ ║ ║ │ │ Conditions:IEnumerable │ ¦ │ ║ +║ │ │ Policies:IEnumerable │ ¦ │ ║ ║ │ │ Cache:Bool │ ¦ │ ║ ║ │ │ Route:IRoute │ ¦ │ ║ ║ │ └────────────────────Δ───────────────────┘ ¦ │ ║ @@ -1616,6 +1619,7 @@ Setting page templates are utilized to manage and configure web applications. Ea ║ │ │ PluginContext:IPluginContext │ │ │ ¦ ║ ║ │ │ ApplicationContext:IApplicationContext │ │ │ ¦ ║ ║ │ │ Conditions:IEnumerable │ │ │ ¦ ║ +║ │ │ Policies:IEnumerable │ │ │ ¦ ║ ║ │ │ Cache:Bool │ │ │ ¦ ║ ║ │ │ Route:IRoute │ │ │ ¦ ║ ║ │ └───────────────────Δ────────────────────┘ │ │ ¦ ║ @@ -1824,8 +1828,7 @@ The following code selection contains an example class called `MyRestApi` that i ```csharp [Version(1)] -[Authorization(Permission.RWX, IdentityPolicyDefault.SystemAccess)] -[Authorization(Permission.R, IdentityPolicyDefault.PublicAccess)] +[Policy] public sealed class MyRestApi : IRestApiCrud { [Method(RequestMethod.POST)] @@ -1850,7 +1853,7 @@ This class uses various attributes to define the CRUD operations. Below are the |SegmentInt |Parameter, String |1 |Yes |A variable path segment of type `Int`. |SegmentGuid |Parameter, String |1 |Yes |A variable path segment of type `Guid`. |IncludeSubPaths |Bool |1 |Yes |Determines whether all resources below the specified path (including segment) are processed. -|Authorization |Int, String |n |Yes |Grants authority to a policy (specifying the id) (see section notification model). +|Policy |`IIdentityPolicy` |n |Yes |Grants authority to a policy. |Condition |`ICondition` |n |Yes |Condition that must be met for the resource to be available. |Cache |- |1 |Yes |Determines whether the resource is created once and reused each time it is called. @@ -1918,6 +1921,7 @@ The following diagram outlines how the class structure and interactions for the ║ │ │ PluginContext:IPluginContext │ ¦ │ ║ ║ │ │ ApplicationContext:IApplicationContext │ ¦ │ ║ ║ │ │ Conditions:IEnumerable │ ¦ │ ║ +║ │ │ Policies:IEnumerable │ ¦ │ ║ ║ │ │ Cache:Bool │ ¦ │ ║ ║ │ │ Route:IRoute │ ¦ │ ║ ║ │ └───────────────────Δ────────────────────┘ ¦ │ ║ @@ -2111,7 +2115,7 @@ An established WebSocket connection is represented at runtime by a dedicated soc |Attribute |Type |Multiplicity |Optional |Description |---------------|-----------------------|-------------|---------|------------- -|Authorization |Int, String |n |Yes |Grants authority to a policy (specifying the id) (see section notification model). +|Policy |`IIdentityPolicy` |n |Yes |Grants authority to a policy. |Condition |`ICondition` |n |Yes |Condition that must be met for the resource to be available. |MessageType |`MessageTypeAttribute` |1 |Yes |Defines the message type and optionally the maximum allowed message size. |SubProtocol |String |1 |Yes |Specifies the sub‑protocol that the socket must use. @@ -2123,8 +2127,7 @@ The example implements the `ISocket` interface in the `MySocket` class, demonstr [MessageType(MaxMessageSize.Text)] [SubProtocol("chat")] [MaxMessageSize(1024)] -[Authorization(Permission.RWX, IdentityPolicyDefault.SystemAccess)] -[Authorization(Permission.R, IdentityPolicyDefault.PublicAccess)] +[Policy] public sealed class MySocket : ISocket { /// @@ -2202,6 +2205,7 @@ The UML diagram illustrates the class structure and interactions for web socket ║ │ │ PluginContext:IPluginContext │ ¦ │ ║ ║ │ │ ApplicationContext:IApplicationContext │ ¦ │ ║ ║ │ │ Conditions:IEnumerable │ ¦ │ ║ +║ │ │ Policies:IEnumerable │ ¦ │ ║ ║ │ │ Cache:Bool │ ¦ │ ║ ║ │ │ Route:IRoute │ ¦ │ ║ ║ │ └───────────────────Δ────────────────────┘ ¦ │ ║ @@ -2512,6 +2516,7 @@ Fragments are components that can be integrated into pages to extend functionali ║ │ PluginContext:IPluginContext │ ¦ ║ ║ │ ApplicationContext:IApplicationContext │ ¦ ║ ║ │ Conditions:IEnumerable │ ¦ ║ +║ │ Policies:IEnumerable │ ¦ ║ ║ │ Cache:Bool │ ¦ ║ ║ └────────────────────────────────────────┘ ¦ ║ ║ ¦ ║ From 0bed4ab8a07e024ec26454b2759092c4e5ca8df5 Mon Sep 17 00:00:00 2001 From: Rene Schwarzer Date: Mon, 13 Apr 2026 04:42:03 +0200 Subject: [PATCH 3/7] feat: general improvements and minor bugs --- docs/development_guide.md | 70 +++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/docs/development_guide.md b/docs/development_guide.md index 1ab00f0..7efc390 100644 --- a/docs/development_guide.md +++ b/docs/development_guide.md @@ -3847,36 +3847,42 @@ Identities and groups must be loaded from a persistent data source, which may be ║ ║ ║ ┌──────────────────────────────────┐ ║ ║ │ <> │ ║ -║ │ IComponentHub │ ║ -║ ├──────────────────────────────────┤ 1 ║ -║ │ IdentityManager:IIdentityManager ├───────────────┐ ║ -║ │ … │ │ ║ -║ └──────────────────────────────────┘ │ ║ -║ │ ║ -║ ┌───────────────────┐ │ ║ -║ │ <> │ │ ║ -║ │ IComponentManager │ │ ║ -║ ├───────────────────┤ │ ║ -║ └────────Δ──────────┘ │ ║ -║ ¦ │ ║ -║ ¦ 1 │ ║ -║ ┌───────┴─────────────────────────▼───────────┐ ║ -║ │ <> │ ║ -║ ┌------------------------┤ IIdentityManager │ ║ -║ ¦ ├─────────────────────────────────────────────┤ ║ -║ ¦ │ Identities:IEnumerable │ ║ -║ ¦ │ Groups:IEnumerable │ ║ -║ ¦ │ Policies:IEnumerable │ ║ -║ ¦ │ Permission:IEnumerable │ ║ -║ ¦ ├─────────────────────────────────────────────┤ ║ -║ ¦ │ AddIdentity(IIdentity) │ ║ -║ ¦ │ AddGroup(IIdentityGroup) │ ║ -║ ¦ │ RemoveIdentity(IIdentity) │ ║ -║ ¦ │ RemoveGroup(IIdentityGroup) │ ║ -║ ¦ │ Login(IApplicationContext,Login,Password) │ ║ -║ ¦ │ Logout(IApplicationContext) │ ║ -║ ¦ │ ComputeHash(SecureString):String │ ║ -║ ¦ └─────┬──────────┬───────────┬──────────┬─────┘ ║ +║ │ IComponentHub │ ┌───────────────────────────────┐ ║ +║ ├──────────────────────────────────┤ 1 │ <> │ ║ +║ │ IdentityManager:IIdentityManager ├────┐ │ IIdentityProvider │ ║ +║ │ … │ │ ├───────────────────────────────┤ ║ +║ └──────────────────────────────────┘ │ ├───────────────────────────────┤ ║ +║ │ │ GetIdentities: │ ║ +║ ┌───────────────────┐ │ │ IEnumerable │ ║ +║ │ <> │ │ │ GetGroups: │ ║ +║ │ IComponentManager │ │ │ IEnumerable │ ║ +║ ├───────────────────┤ │ │ ValidateCredentials:Bool │ ║ +║ └────────Δ──────────┘ │ └─────────────────────────▲─────┘ ║ +║ ¦ │ * │ ║ +║ ¦ 1 │ │ ║ +║ ┌────────┴──────────────▼───────────────────────┐ 1 │ ║ +║ │ <> ├────┘ ║ +║ ┌-----------------------┤ IIdentityManager │ ║ +║ ¦ ├───────────────────────────────────────────────┤ ║ +║ ¦ │ Policies:IEnumerable │ ║ +║ ¦ │ Permission:IEnumerable │ ║ +║ ¦ ├───────────────────────────────────────────────┤ ║ +║ ¦ │ AddIdentity(IIdentity) │ ║ +║ ¦ │ AddGroup(IIdentityGroup) │ ║ +║ ¦ │ RemoveIdentity(IIdentity) │ ║ +║ ¦ │ RemoveGroup(IIdentityGroup) │ ║ +║ ¦ │ Login(IApplicationContext,Login,Password) │ ║ +║ ¦ │ Logout(IApplicationContext) │ ║ +║ ¦ │ ComputeHash(SecureString):String │ ║ +║ ¦ │ RegisterIdentityProvider(IIdentityProvider, │ ║ +║ ¦ │ IApplicationContext) │ ║ +║ ¦ │ UnregisterIdentityProvider(IIdentityProvider, │ ║ +║ ¦ │ IApplicationContext) │ ║ +║ ¦ │ GetIdentities(IApplicationContext): │ ║ +║ ¦ │ IEnumerable │ ║ +║ ¦ │ GetGroups(IApplicationContext): │ ║ +║ ¦ │ IEnumerable │ ║ +║ ¦ └──────┬──────────┬───────────┬──────────┬──────┘ ║ ║ ¦ 1 │ 1 │ 1 │ 1 │ ║ ║ ¦ ┌────────────┘ │ │ └─────┐ ║ ║ ¦ │ ┌──┘ │ │ ║ @@ -3888,7 +3894,7 @@ Identities and groups must be loaded from a persistent data source, which may be ║ ¦ │ Id:Guid │ │ │ │ ║ ║ ¦ │ Name:String │ │ │ │ ║ ║ ¦ │ EMail:String │ │ │ │ ║ -║ ¦ │ State:AccountState │ │ │ │ ║ +║ ¦ │ State:IdentityState │ │ │ │ ║ ║ ¦ │ Groups: │ │ │ │ ║ ║ ¦ │ IEnumerable │ │ │ │ ║ ║ ¦ ├───────────────────────────────┤ │ │ │ ║ @@ -3941,7 +3947,7 @@ Identities and groups must be loaded from a persistent data source, which may be ║ ¦ │ Id:Guid │ ¦ ¦ ¦ ║ ║ ¦ │ Name:String │ ¦ ¦ ¦ ║ ║ ¦ │ EMail:String │ ¦ ¦ ¦ ║ -║ ¦ │ State:AccountState │1 ¦ ¦ ¦ ║ +║ ¦ │ State:IdentityState │1 ¦ ¦ ¦ ║ ║ ¦ │ Groups: ├───¦─────┐ ¦ ¦ ║ ║ ¦ │ IEnumerable │ ¦ │ ¦ ¦ ║ ║ ¦ ├───────────────────────────────┤ ¦ │ ¦ ¦ ║ From 459308db3066c92ca90fcfc16538cee29fc042ce Mon Sep 17 00:00:00 2001 From: Rene Schwarzer Date: Tue, 14 Apr 2026 09:09:48 +0200 Subject: [PATCH 4/7] feat: general improvements and minor bugs --- docs/development_guide.md | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/docs/development_guide.md b/docs/development_guide.md index 7efc390..63719a7 100644 --- a/docs/development_guide.md +++ b/docs/development_guide.md @@ -3845,18 +3845,20 @@ Identities and groups must be loaded from a persistent data source, which may be ``` ╔WebExpress.Core═══════════════════════════════════════════════════════════════════════╗ ║ ║ -║ ┌──────────────────────────────────┐ ║ -║ │ <> │ ║ -║ │ IComponentHub │ ┌───────────────────────────────┐ ║ -║ ├──────────────────────────────────┤ 1 │ <> │ ║ -║ │ IdentityManager:IIdentityManager ├────┐ │ IIdentityProvider │ ║ -║ │ … │ │ ├───────────────────────────────┤ ║ -║ └──────────────────────────────────┘ │ ├───────────────────────────────┤ ║ -║ │ │ GetIdentities: │ ║ -║ ┌───────────────────┐ │ │ IEnumerable │ ║ -║ │ <> │ │ │ GetGroups: │ ║ -║ │ IComponentManager │ │ │ IEnumerable │ ║ -║ ├───────────────────┤ │ │ ValidateCredentials:Bool │ ║ +║ ┌───────────────────────────────┐ ║ +║ │ <> │ ║ +║ ┌──────────────────────────────────┐ │ IIdentityProvider │ ║ +║ │ <> │ ├───────────────────────────────┤ ║ +║ │ IComponentHub │ ├───────────────────────────────┤ ║ +║ ├──────────────────────────────────┤ 1 │ GetIdentities: │ ║ +║ │ IdentityManager:IIdentityManager ├────┐ │ IEnumerable │ ║ +║ │ … │ │ │ GetGroups: │ ║ +║ └──────────────────────────────────┘ │ │ IEnumerable │ ║ +║ │ │ Authenticate(IRequest): │ ║ +║ ┌───────────────────┐ │ │ IIdentity │ ║ +║ │ <> │ │ │ CreateAuthenticationPrompt( │ ║ +║ │ IComponentManager │ │ │ IRequest,IEndpointContext, │ ║ +║ ├───────────────────┤ │ │ IIdentity):IResponse │ ║ ║ └────────Δ──────────┘ │ └─────────────────────────▲─────┘ ║ ║ ¦ │ * │ ║ ║ ¦ 1 │ │ ║ @@ -3871,8 +3873,12 @@ Identities and groups must be loaded from a persistent data source, which may be ║ ¦ │ AddGroup(IIdentityGroup) │ ║ ║ ¦ │ RemoveIdentity(IIdentity) │ ║ ║ ¦ │ RemoveGroup(IIdentityGroup) │ ║ -║ ¦ │ Login(IApplicationContext,Login,Password) │ ║ -║ ¦ │ Logout(IApplicationContext) │ ║ +║ ¦ │ Authenticate(IRequest,IApplicationContext): │ ║ +║ ¦ │ IIdentity │ ║ +║ ¦ │ CreateAuthenticationPrompt(IRequest, │ ║ +║ ¦ │ IEndpointContext,Identity):IResponse │ ║ +║ ¦ │ Login(IRequest,IIdentity):Bool │ ║ +║ ¦ │ Logout(IRequest) │ ║ ║ ¦ │ ComputeHash(SecureString):String │ ║ ║ ¦ │ RegisterIdentityProvider(IIdentityProvider, │ ║ ║ ¦ │ IApplicationContext) │ ║ @@ -3882,6 +3888,7 @@ Identities and groups must be loaded from a persistent data source, which may be ║ ¦ │ IEnumerable │ ║ ║ ¦ │ GetGroups(IApplicationContext): │ ║ ║ ¦ │ IEnumerable │ ║ +║ ¦ │ CheckAccess(IIdentity,IEndpointContext):Bool │ ║ ║ ¦ └──────┬──────────┬───────────┬──────────┬──────┘ ║ ║ ¦ 1 │ 1 │ 1 │ 1 │ ║ ║ ¦ ┌────────────┘ │ │ └─────┐ ║ From 170475307c0e4d3a339d7c5d3e8f46ac89c55628 Mon Sep 17 00:00:00 2001 From: Rene Schwarzer Date: Thu, 16 Apr 2026 10:52:50 +0200 Subject: [PATCH 5/7] feat: general improvements and minor bugs --- docs/development_guide.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/development_guide.md b/docs/development_guide.md index 63719a7..187b796 100644 --- a/docs/development_guide.md +++ b/docs/development_guide.md @@ -3854,8 +3854,9 @@ Identities and groups must be loaded from a persistent data source, which may be ║ │ IdentityManager:IIdentityManager ├────┐ │ IEnumerable │ ║ ║ │ … │ │ │ GetGroups: │ ║ ║ └──────────────────────────────────┘ │ │ IEnumerable │ ║ -║ │ │ Authenticate(IRequest): │ ║ -║ ┌───────────────────┐ │ │ IIdentity │ ║ +║ │ │ CreateForbiddenPage( │ ║ +║ │ │ IRequest,IEndpointContext, │ ║ +║ ┌───────────────────┐ │ │ IIdentity):IResponse │ ║ ║ │ <> │ │ │ CreateAuthenticationPrompt( │ ║ ║ │ IComponentManager │ │ │ IRequest,IEndpointContext, │ ║ ║ ├───────────────────┤ │ │ IIdentity):IResponse │ ║ @@ -3873,8 +3874,8 @@ Identities and groups must be loaded from a persistent data source, which may be ║ ¦ │ AddGroup(IIdentityGroup) │ ║ ║ ¦ │ RemoveIdentity(IIdentity) │ ║ ║ ¦ │ RemoveGroup(IIdentityGroup) │ ║ -║ ¦ │ Authenticate(IRequest,IApplicationContext): │ ║ -║ ¦ │ IIdentity │ ║ +║ ¦ │ CreateForbiddenPage(IRequest, │ ║ +║ ¦ │ IEndpointContext,Identity):IResponse │ ║ ║ ¦ │ CreateAuthenticationPrompt(IRequest, │ ║ ║ ¦ │ IEndpointContext,Identity):IResponse │ ║ ║ ¦ │ Login(IRequest,IIdentity):Bool │ ║ From 865a0cc39faa1877c8d6d2c3c7bcbaf045ed0b1d Mon Sep 17 00:00:00 2001 From: Rene Schwarzer Date: Sat, 16 May 2026 08:28:23 +0200 Subject: [PATCH 6/7] feat: add project landing page --- .github/workflows/generate-docs.yml | 69 +++ docs/development_guide.md | 19 +- site/.nojekyll | 0 site/404.html | 26 ++ site/README.md | 33 ++ site/assets/css/styles.css | 664 ++++++++++++++++++++++++++++ site/assets/img/favicon.svg | 22 + site/assets/img/logo.svg | 22 + site/assets/js/main.js | 37 ++ site/imprint.html | 72 +++ site/index.html | 293 ++++++++++++ site/privacy.html | 80 ++++ site/robots.txt | 4 + site/sitemap.xml | 18 + 14 files changed, 1350 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/generate-docs.yml create mode 100644 site/.nojekyll create mode 100644 site/404.html create mode 100644 site/README.md create mode 100644 site/assets/css/styles.css create mode 100644 site/assets/img/favicon.svg create mode 100644 site/assets/img/logo.svg create mode 100644 site/assets/js/main.js create mode 100644 site/imprint.html create mode 100644 site/index.html create mode 100644 site/privacy.html create mode 100644 site/robots.txt create mode 100644 site/sitemap.xml diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml new file mode 100644 index 0000000..85db9c5 --- /dev/null +++ b/.github/workflows/generate-docs.yml @@ -0,0 +1,69 @@ +name: Generate Docs + +on: + push: + branches: [main] + paths: + - 'site/**' + - 'README.md' + - 'CONTRIBUTING.md' + - 'docs/**' + - '.github/workflows/generate-docs.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + name: Build landing page + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Stage static site + run: | + set -euo pipefail + mkdir -p _site + cp -r site/. _site/ + # disable Jekyll processing so files are served verbatim + touch _site/.nojekyll + # record build metadata (no personal data) + date -u +"%Y-%m-%dT%H:%M:%SZ" > _site/build.txt + + - name: Validate (no external resources) + run: | + set -euo pipefail + # fail the build if any HTML file references third-party origins, which would + # break GDPR compliance and the "no external requests" guarantee + if grep -RInE 'https?://(cdn\.|fonts\.googleapis\.com|fonts\.gstatic\.com|www\.google-analytics\.com|googletagmanager\.com)' _site --include='*.html'; then + echo "ERROR: external resource reference found — landing page must be self-contained." >&2 + exit 1 + fi + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: _site + + deploy: + name: Deploy to GitHub Pages + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/development_guide.md b/docs/development_guide.md index 187b796..4ca7ae6 100644 --- a/docs/development_guide.md +++ b/docs/development_guide.md @@ -546,14 +546,15 @@ public sealed class MyApplication : Application To provide clarity about the metadata specified in the code above, the following table presents the available attributes and their corresponding details for defining applications: -|Attribute |Type |Multiplicity |Optional |Description -|------------|-----------|-------------|---------|------------ -|Name |String |1 |Yes |The name of the application. This can be a key to internationalization. -|Description |String |1 |Yes |The description of the application. This can be a key to internationalization. -|Icon |String |1 |Yes |The icon that represents the application graphically. -|AssetPath |String |1 |Yes |The path where the assets are stored. This file path is mounted in the asset path of the web server. -|DataPath |String |1 |Yes |The path where the data is stored. This file path is mounted in the data path of the web server. -|ContextPath |String |1 |Yes |The context path where the resources are stored. This path is mounted in the context path of the web server. +|Attribute |Type |Multiplicity |Optional |Description +|------------|----------------|-------------|---------|------------ +|Name |String |1 |Yes |The name of the application. This can be a key to internationalization. +|Description |String |1 |Yes |The description of the application. This can be a key to internationalization. +|Icon |String |1 |Yes |The icon that represents the application graphically. +|IconTheme |`TypeIconTheme` |1 |Yes |The theme applied to the icon, defining its visual style (e.g., light). +|AssetPath |String |1 |Yes |The path where the assets are stored. This file path is mounted in the asset path of the web server. +|DataPath |String |1 |Yes |The path where the data is stored. This file path is mounted in the data path of the web server. +|ContextPath |String |1 |Yes |The context path where the resources are stored. This path is mounted in the context path of the web server. The methods implemented from the interface cover the life cycle of the application. When the plugin is loaded, all the applications it contains are instantiated. These remain in place until the plugin is unloaded. Meta information about the application is stored in the `ApplicationContext` and managed by the `ApplicationManager`. To better understand the organization and lifecycle of applications in relation to the `ApplicationManager`, refer to the UML diagram below: @@ -4749,7 +4750,7 @@ namespace Sample { public void Render(IRenderContext renderContext, VisualTree visualTree) { - var control = new ControlText(){Text = "Hello World!"}; + var control = new ControlText(){Text = _ => "Hello World!"}; visualTree.AddContent(control); } diff --git a/site/.nojekyll b/site/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/site/404.html b/site/404.html new file mode 100644 index 0000000..d1ed7b7 --- /dev/null +++ b/site/404.html @@ -0,0 +1,26 @@ + + + + + + + + Page not found — WebExpress + + + + + +
+
+

Error 404

+

This page is not part of the WebExpress site.

+

The URL you requested does not exist here. From the home page you can reach the documentation, the contribution guide and the project on GitHub.

+

+ Back to home + View on GitHub +

+
+
+ + diff --git a/site/README.md b/site/README.md new file mode 100644 index 0000000..66b052f --- /dev/null +++ b/site/README.md @@ -0,0 +1,33 @@ +# WebExpress landing page + +This folder is the source of the static landing page that is deployed to GitHub Pages by `.github/workflows/generate-docs.yml`. + +## What is in here + +- `index.html` — landing page (hero, getting started, contribute, contact, legal). +- `privacy.html` — GDPR privacy notice. +- `imprint.html` — imprint (operator must complete the postal address). +- `404.html` — error page. +- `assets/css/styles.css` — local stylesheet, system fonts only. +- `assets/js/main.js` — small enhancement script (mobile menu, copyright year). No external requests. +- `assets/img/` — inline SVG logo, favicon and OpenGraph card. +- `robots.txt`, `sitemap.xml`, `.nojekyll` — site metadata. + +## Design constraints + +- No external resources (no fonts.googleapis.com, no CDN scripts, no analytics). +- No cookies, no `localStorage`, no `sessionStorage`. +- System fonts only. +- Responsive, mobile-first, WCAG AA in mind. + +The workflow validates that no third-party origins are referenced from any +HTML file before it deploys to Pages. + +## Local preview + +Open `index.html` directly in a browser, or serve the folder with any static +file server, e.g.: + +``` +python -m http.server --directory site 8080 +``` diff --git a/site/assets/css/styles.css b/site/assets/css/styles.css new file mode 100644 index 0000000..a2b39d8 --- /dev/null +++ b/site/assets/css/styles.css @@ -0,0 +1,664 @@ +/* ============================================================================ + * WebExpress landing page styles + * - Mobile-first, responsive, accessible + * - GDPR-friendly: system fonts only, no @import of external stylesheets, + * no remote URLs, no external trackers. + * ============================================================================ */ + +/* --- design tokens ---------------------------------------------------------- */ +:root { + --color-primary: #2563eb; + --color-primary-dark: #1e40af; + --color-accent: #0ea5e9; + --color-bg: #ffffff; + --color-surface: #f8fafc; + --color-surface-2: #eef2f7; + --color-text: #0f172a; + --color-text-muted: #475569; + --color-border: #e2e8f0; + --color-code-bg: #0f172a; + --color-code-text: #e2e8f0; + + --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; + + --radius-sm: 6px; + --radius-md: 12px; + --radius-lg: 18px; + + --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.06); + --shadow-md: 0 6px 18px rgba(15, 23, 42, 0.08); + + --transition: 180ms ease-out; + + --container-max: 1100px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-bg: #0b1220; + --color-surface: #111a2e; + --color-surface-2: #14223d; + --color-text: #e2e8f0; + --color-text-muted: #94a3b8; + --color-border: #1f2a44; + --color-primary: #60a5fa; + --color-primary-dark: #93c5fd; + --color-accent: #38bdf8; + } +} + +/* --- reset & base ----------------------------------------------------------- */ +*, *::before, *::after { + box-sizing: border-box; +} + +html { + -webkit-text-size-adjust: 100%; + scroll-behavior: smooth; +} + +body { + margin: 0; + font-family: var(--font-sans); + font-size: 1rem; + line-height: 1.6; + color: var(--color-text); + background-color: var(--color-bg); + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; +} + +img, svg { + display: block; + max-width: 100%; +} + +a { + color: var(--color-primary); + text-decoration: none; + transition: color var(--transition); +} + +a:hover, +a:focus-visible { + color: var(--color-primary-dark); + text-decoration: underline; +} + +:focus-visible { + outline: 3px solid var(--color-accent); + outline-offset: 2px; + border-radius: var(--radius-sm); +} + +h1, h2, h3, h4 { + line-height: 1.2; + margin: 0 0 0.5em; + color: var(--color-text); +} + +p { + margin: 0 0 1em; +} + +code, pre { + font-family: var(--font-mono); + font-size: 0.92em; +} + +code { + background: var(--color-surface-2); + color: var(--color-text); + padding: 0.1em 0.35em; + border-radius: var(--radius-sm); +} + +pre { + background: var(--color-code-bg); + color: var(--color-code-text); + padding: 1.25rem 1.5rem; + border-radius: var(--radius-md); + overflow-x: auto; + box-shadow: var(--shadow-sm); +} + +pre code { + background: transparent; + color: inherit; + padding: 0; +} + +/* --- utilities -------------------------------------------------------------- */ +.container { + width: 100%; + max-width: var(--container-max); + margin: 0 auto; + padding-inline: 1.25rem; +} + +.visually-hidden { + position: absolute; + width: 1px; height: 1px; + padding: 0; margin: -1px; + overflow: hidden; clip: rect(0,0,0,0); + white-space: nowrap; border: 0; +} + +.skip-link { + position: absolute; + inset-inline-start: 1rem; + top: -3rem; + background: var(--color-primary); + color: #fff; + padding: 0.6rem 1rem; + border-radius: var(--radius-sm); + z-index: 1000; + transition: top var(--transition); +} + +.skip-link:focus-visible { + top: 1rem; + color: #fff; + text-decoration: none; +} + +.muted { + color: var(--color-text-muted); +} + +.small { + font-size: 0.875rem; +} + +.eyebrow { + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 0.78rem; + font-weight: 600; + color: var(--color-primary); + margin: 0 0 0.5rem; +} + +/* --- header ----------------------------------------------------------------- */ +.site-header { + position: sticky; + top: 0; + z-index: 50; + background: color-mix(in srgb, var(--color-bg) 88%, transparent); + backdrop-filter: blur(8px); + border-bottom: 1px solid var(--color-border); +} + +.site-header__inner { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 64px; + gap: 1.5rem; +} + +.brand { + display: inline-flex; + align-items: center; + gap: 0.6rem; + font-weight: 700; + color: var(--color-text); + letter-spacing: -0.01em; +} + +.brand:hover, +.brand:focus-visible { + color: var(--color-text); + text-decoration: none; +} + +.brand__name { + font-size: 1.1rem; +} + +.brand--footer .brand__name { + font-size: 1rem; +} + +.site-nav { + display: flex; + align-items: center; +} + +.site-nav__list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + gap: 0.5rem; + align-items: center; +} + +.site-nav__list a { + display: inline-block; + padding: 0.5rem 0.85rem; + border-radius: var(--radius-sm); + color: var(--color-text); + font-weight: 500; + font-size: 0.95rem; +} + +.site-nav__list a:hover, +.site-nav__list a:focus-visible { + background: var(--color-surface); + color: var(--color-primary-dark); + text-decoration: none; +} + +.site-nav__toggle { + display: none; + background: transparent; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + width: 40px; height: 40px; + cursor: pointer; + padding: 0; + align-items: center; + justify-content: center; + color: var(--color-text); +} + +.site-nav__icon, +.site-nav__icon::before, +.site-nav__icon::after { + display: block; + width: 18px; height: 2px; + background: currentColor; + border-radius: 2px; + position: relative; +} +.site-nav__icon::before, +.site-nav__icon::after { + content: ""; + position: absolute; + inset-inline-start: 0; +} +.site-nav__icon::before { top: -6px; } +.site-nav__icon::after { top: 6px; } + +@media (max-width: 720px) { + .site-nav__toggle { + display: inline-flex; + } + .site-nav__list { + position: absolute; + inset-inline: 0; + top: 64px; + flex-direction: column; + align-items: stretch; + gap: 0; + padding: 0.75rem 1.25rem 1rem; + background: var(--color-bg); + border-bottom: 1px solid var(--color-border); + display: none; + } + .site-nav__list[data-open="true"] { + display: flex; + } + .site-nav__list a { + padding: 0.75rem 0.5rem; + border-radius: 0; + border-bottom: 1px solid var(--color-border); + } + .site-nav__list li:last-child a { + border-bottom: none; + } +} + +/* --- buttons ---------------------------------------------------------------- */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.75rem 1.25rem; + border-radius: var(--radius-md); + font-weight: 600; + font-size: 0.97rem; + line-height: 1; + border: 1px solid transparent; + cursor: pointer; + transition: transform var(--transition), background var(--transition), border-color var(--transition), color var(--transition), box-shadow var(--transition); + text-decoration: none; +} + +.btn:hover, +.btn:focus-visible { + text-decoration: none; + transform: translateY(-1px); +} + +.btn:active { + transform: translateY(0); +} + +.btn--primary { + background: var(--color-primary); + color: #ffffff; + box-shadow: var(--shadow-sm); +} +.btn--primary:hover, +.btn--primary:focus-visible { + background: var(--color-primary-dark); + color: #ffffff; + box-shadow: var(--shadow-md); +} + +.btn--ghost { + background: transparent; + border-color: var(--color-border); + color: var(--color-text); +} +.btn--ghost:hover, +.btn--ghost:focus-visible { + border-color: var(--color-primary); + color: var(--color-primary-dark); + background: var(--color-surface); +} + +/* --- hero ------------------------------------------------------------------- */ +.hero { + position: relative; + overflow: hidden; + padding-block: clamp(3rem, 8vw, 6rem); + background: linear-gradient(180deg, var(--color-surface) 0%, var(--color-bg) 100%); + border-bottom: 1px solid var(--color-border); +} + +.hero__bg { + position: absolute; + inset: 0; + z-index: 0; + pointer-events: none; +} + +.hero__svg { + width: 100%; + height: 100%; +} + +.hero__inner { + position: relative; + z-index: 1; + max-width: 820px; +} + +.hero__title { + font-size: clamp(2rem, 4.5vw, 3.25rem); + letter-spacing: -0.02em; + margin-bottom: 1rem; +} + +.hero__lead { + font-size: clamp(1.05rem, 1.4vw, 1.2rem); + color: var(--color-text-muted); + max-width: 65ch; + margin-bottom: 2rem; +} + +.hero__actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-bottom: 2.25rem; +} + +.hero__highlights { + list-style: none; + padding: 0; + margin: 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 0.6rem 1.5rem; + color: var(--color-text-muted); + font-size: 0.97rem; +} + +.hero__highlights li { + display: flex; + align-items: center; + gap: 0.5rem; +} + +/* --- sections --------------------------------------------------------------- */ +.section { + padding-block: clamp(2.5rem, 7vw, 5rem); +} + +.section--alt { + background: var(--color-surface); + border-block: 1px solid var(--color-border); +} + +.section__header { + max-width: 720px; + margin-bottom: 2rem; +} + +.section__header h2 { + font-size: clamp(1.6rem, 3vw, 2.2rem); + letter-spacing: -0.015em; +} + +.section__lead { + color: var(--color-text-muted); + font-size: 1.05rem; + margin: 0; +} + +.section__footnote { + margin-top: 1.5rem; + color: var(--color-text-muted); + font-size: 0.95rem; +} + +/* --- cards ------------------------------------------------------------------ */ +.cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 1.25rem; +} + +.cards--two { + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + max-width: 920px; +} + +.card { + background: var(--color-bg); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: 1.5rem; + box-shadow: var(--shadow-sm); + transition: transform var(--transition), box-shadow var(--transition), border-color var(--transition); +} + +.section--alt .card { + background: var(--color-bg); +} + +.card:hover, +.card:focus-within { + transform: translateY(-2px); + box-shadow: var(--shadow-md); + border-color: color-mix(in srgb, var(--color-primary) 35%, var(--color-border)); +} + +.card__title { + margin: 0 0 0.5rem; + font-size: 1.2rem; +} + +.link-list, +.bullet-list { + margin: 1rem 0 0; + padding-inline-start: 1.1rem; +} + +.link-list { + list-style: "→ "; +} + +.link-list li, +.bullet-list li { + margin-bottom: 0.35rem; +} + +.bullet-list { + list-style: disc; +} + +/* --- code snippet ----------------------------------------------------------- */ +.snippet { + margin-top: 2rem; +} + +.snippet__caption { + color: var(--color-text-muted); + font-size: 0.92rem; + margin: 0 0 0.5rem; +} + +/* --- legal grid ------------------------------------------------------------- */ +.legal-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 1rem; +} + +.legal-box { + padding: 1.25rem 1.4rem; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: var(--color-bg); +} + +.legal-box h3 { + margin: 0 0 0.4rem; + font-size: 1.02rem; +} + +.legal-box p { + margin: 0; + color: var(--color-text-muted); + font-size: 0.95rem; +} + +/* --- footer ----------------------------------------------------------------- */ +.site-footer { + background: var(--color-surface); + border-top: 1px solid var(--color-border); + padding-block: 2.5rem 1.5rem; +} + +.site-footer__inner { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 1.5rem 2rem; + margin-bottom: 1.5rem; +} + +.site-footer__col h4 { + font-size: 0.92rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--color-text-muted); + margin: 0 0 0.6rem; +} + +.site-footer__col ul { + list-style: none; + padding: 0; + margin: 0; +} + +.site-footer__col li { + margin-bottom: 0.35rem; +} + +.site-footer__col a { + color: var(--color-text); +} + +.site-footer__col a:hover, +.site-footer__col a:focus-visible { + color: var(--color-primary-dark); +} + +.site-footer__meta { + border-top: 1px solid var(--color-border); + padding-top: 1rem; + color: var(--color-text-muted); + font-size: 0.85rem; +} + +/* --- contact ---------------------------------------------------------------- */ +.contact-mail { + font-family: var(--font-mono); + font-size: 1.05rem; + word-break: break-all; +} + +/* --- legal page ------------------------------------------------------------- */ +.legal-page { + padding-block: clamp(2rem, 5vw, 3.5rem); +} + +.legal-page h1 { + font-size: clamp(1.7rem, 3vw, 2.25rem); + letter-spacing: -0.015em; +} + +.legal-page h2 { + margin-top: 2rem; + font-size: 1.2rem; +} + +.legal-page p { + max-width: 70ch; +} + +.back-link { + margin-top: 2.5rem; +} + +/* --- 404 page --------------------------------------------------------------- */ +.error-body { + display: flex; + align-items: center; + min-height: 100vh; +} + +.error-page { + width: 100%; +} + +.error-page__inner { + text-align: center; + padding-block: 4rem; +} + +.error-page__inner h1 { + font-size: clamp(1.6rem, 3vw, 2.2rem); + margin-bottom: 1rem; +} + +.error-page__inner .btn { + margin: 0.3rem; +} + +/* --- motion preferences ----------------------------------------------------- */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} diff --git a/site/assets/img/favicon.svg b/site/assets/img/favicon.svg new file mode 100644 index 0000000..fa74fa2 --- /dev/null +++ b/site/assets/img/favicon.svg @@ -0,0 +1,22 @@ + + + + + diff --git a/site/assets/img/logo.svg b/site/assets/img/logo.svg new file mode 100644 index 0000000..fa74fa2 --- /dev/null +++ b/site/assets/img/logo.svg @@ -0,0 +1,22 @@ + + + + + diff --git a/site/assets/js/main.js b/site/assets/js/main.js new file mode 100644 index 0000000..cb2b693 --- /dev/null +++ b/site/assets/js/main.js @@ -0,0 +1,37 @@ +/* + * WebExpress landing — local-only behavior. + * No network requests, no analytics, no cookies, no storage access. + */ +(function () { + "use strict"; + + // mobile menu toggle + var toggle = document.querySelector(".site-nav__toggle"); + var menu = document.getElementById("primary-menu"); + + if (toggle && menu) { + toggle.addEventListener("click", function () { + var open = menu.getAttribute("data-open") === "true"; + menu.setAttribute("data-open", open ? "false" : "true"); + toggle.setAttribute("aria-expanded", open ? "false" : "true"); + }); + + // close the menu when a link inside it is activated + menu.addEventListener("click", function (event) { + var target = event.target; + if (target && target.tagName === "A") { + menu.setAttribute("data-open", "false"); + toggle.setAttribute("aria-expanded", "false"); + } + }); + } + + // keep the copyright year in sync without exposing any external data + var yearNodes = document.querySelectorAll("[data-current-year]"); + if (yearNodes.length > 0) { + var year = String(new Date().getFullYear()); + for (var i = 0; i < yearNodes.length; i++) { + yearNodes[i].textContent = year; + } + } +})(); diff --git a/site/imprint.html b/site/imprint.html new file mode 100644 index 0000000..eb936a9 --- /dev/null +++ b/site/imprint.html @@ -0,0 +1,72 @@ + + + + + + + + Imprint — WebExpress + + + + + + + + + + + +
+
+

Imprint

+ +

Responsible for the content

+

+ WebExpress — open-source project
+ Maintainer: René Schwarzer
+ Email: webexpress-framework@outlook.com
+ Project page: github.com/webexpress-framework/WebExpress
+ Postal address: Available upon request where a legitimate interest is demonstrated (in accordance with GDPR Art. 6(1)(f))] +

+ +

Hosting

+

This site is hosted by GitHub Inc., 88 Colin P Kelly Jr St, San Francisco, CA 94107, USA. See the privacy notice for details about the data processed during delivery.

+ +

Liability for content

+

The content of this site has been prepared with care. We do not accept liability for the accuracy, completeness or timeliness of the information. As a service provider we are responsible for our own content on these pages in accordance with general law. We are however not obliged to monitor third-party information transmitted or stored, or to investigate circumstances that indicate illegal activity. Obligations to remove or block the use of information under general law remain unaffected.

+ +

Liability for links

+

This site contains links to external websites operated by third parties. We have no influence over the content of these sites. At the time of linking, no illegal content was identified on the linked pages. A permanent content-related monitoring of linked pages is not reasonable without concrete evidence of a legal infringement. Upon notification of legal infringements we will remove such links immediately.

+ +

Copyright

+

The source code of the WebExpress project is licensed under the MIT license; see the LICENSE file. Content on this site — texts, images, layout — is, unless otherwise marked, licensed under the same terms.

+ + +
+
+ +
+ +
+ + + + diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..27f6809 --- /dev/null +++ b/site/index.html @@ -0,0 +1,293 @@ + + + + + + + + WebExpress — A lightweight web framework for structured UI, REST and clean architecture + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+

.NET web framework

+

A lightweight, modern web framework for structured UI, REST integration and clean architecture.

+

WebExpress is a small-footprint web server with a powerful plugin system and a comprehensive API — built so that web applications can be integrated quickly and cleanly into any .NET language, even on low-performance hardware such as the Raspberry Pi.

+ +
    +
  • Lightweight & efficient
  • +
  • Plugin-first architecture
  • +
  • HTTPS & HTTP/2 ready
  • +
  • Runs on Raspberry Pi
  • +
+
+
+ + +
+
+
+

Introduction

+

Getting Started with WebExpress

+

WebExpress is based on Kestrel and follows a modular, plugin-driven design. Every application is a composition of well-scoped plugins, controls and fragments — the framework keeps the surface small, the runtime fast and the architecture explicit.

+
+ +
+
+

Installation

+

Install the .NET SDK, download the latest WebExpress release and unpack it to your target directory. The installation guide covers Raspberry Pi end-to-end, including HTTPS certificates and systemd integration.

+ +
+ + + + +
+ +
+

Minimal configuration (webexpress.config.xml)

+
<configuration>
+    <endpoint uri="http://*/" />
+    <endpoint uri="https://*:443/" pfx="./ssl/wx.pfx" password="hello" />
+    <culture>en-US</culture>
+    <assets>./</assets>
+    <packages>./packages</packages>
+</configuration>
+
+ +

+ For the full API documentation see the + WebCore API docs + and the + WebUI API docs. +

+
+
+ + +
+
+
+

Community

+

Contribute to WebExpress

+

WebExpress is open source under the MIT license. Bug reports, feature ideas, documentation and translations are all valuable contributions — please open an issue before you start working on larger changes so we can align on scope.

+
+ +
+
+

Ways to contribute

+
    +
  • Report bugs through the GitHub issue tracker.
  • +
  • Suggest features and discuss them in an issue first.
  • +
  • Submit pull requests with tests and documentation.
  • +
  • Improve documentation, tutorials and examples.
  • +
  • Translate the framework into more languages.
  • +
+ +
+ +
+

Guidelines

+
    +
  • Coding style — follow the conventions used across the framework family.
  • +
  • Architecture — keep responsibilities cleanly scoped to Core, UI, App or Index.
  • +
  • Tests — bug fixes ship with a regression test; features ship with coverage.
  • +
  • Documentation — update READMEs, guides or tutorials together with the change.
  • +
+ +
+
+
+
+ + +
+
+
+

Reach out

+

Contact

+

We do not run any external form services or trackers on this site. The fastest way to reach the project is GitHub — for private matters, use the email address below.

+
+ +
+
+

Project communication

+

Open an issue for bugs and feature requests, or start a discussion for questions and ideas. This keeps the conversation public and searchable for everyone. For release notes, articles and background on the project, follow the project blog.

+ +

The blog is hosted externally on Blogger. Visiting it leaves this site; see the privacy notice.

+
+ +
+

Direct contact

+

Reach the maintainer by email. No personal data is stored on this site — your message is sent through your own mail client.

+

+ webexpress-framework@outlook.com +

+

By writing to us you only share the data your own mail client transmits. We process it solely to answer your request.

+
+
+
+
+ + + +
+ + + + + + diff --git a/site/privacy.html b/site/privacy.html new file mode 100644 index 0000000..f263486 --- /dev/null +++ b/site/privacy.html @@ -0,0 +1,80 @@ + + + + + + + + Privacy notice — WebExpress + + + + + + + + + + + +
+
+

Privacy notice

+

Last updated: 2026-05-16

+ +

1. Overview

+

This website is the landing page of the open-source project WebExpress. It is hosted on GitHub Pages and contains only static HTML, CSS, SVG and a small piece of local JavaScript. It does not run a backend, does not set cookies and does not embed any third-party trackers, analytics, fonts or content delivery networks.

+ +

2. Hosting (GitHub Pages)

+

The pages are delivered by GitHub Inc. (88 Colin P Kelly Jr St, San Francisco, CA 94107, USA). When you load a page, GitHub's servers process the technically necessary connection data (in particular: your IP address, the URL of the requested resource, the date and time of the request, the HTTP user agent and the referrer). This processing is required to deliver the page and to maintain the security of the service. The legal basis is Art. 6 (1) (f) GDPR — our legitimate interest in operating a stable, secure website. Details are documented in GitHub's privacy statement.

+ +

3. Cookies and local storage

+

This site does not set any cookies and does not write to localStorage or sessionStorage.

+ +

4. Analytics and tracking

+

We do not use Google Analytics, Plausible, Matomo or any comparable service. No usage statistics are collected on this site.

+ +

5. External resources

+

All fonts, icons, images and stylesheets are served from the same origin as the page itself. No requests to third-party CDNs are made when you visit this site. We deliberately use system fonts so that no external font services are involved.

+ +

6. Embedded content and outbound links

+

The site contains links to GitHub repositories, releases and discussions, as well as to the project blog hosted on Blogger (webexpress-framework.blogspot.com), a service operated by Google LLC. Visiting those linked sites is subject to the privacy statements of their respective operators and may involve cookies and tracking technologies outside our control. No embeds (videos, widgets, social buttons) are loaded on this site.

+ +

7. Contact by email

+

If you contact us by email (see the contact section on the home page), the data you provide in your message (in particular your email address and the content of the message) is processed solely to handle your request. The legal basis is Art. 6 (1) (b) or (f) GDPR. Messages are kept only for as long as necessary to answer your request and to comply with statutory retention obligations.

+ +

8. Your rights under the GDPR

+

You have the right to information (Art. 15), correction (Art. 16), erasure (Art. 17), restriction of processing (Art. 18), data portability (Art. 20) and to object to processing (Art. 21). You also have the right to lodge a complaint with a supervisory authority.

+ +

9. Contact for privacy matters

+

For privacy-related questions please use the contact details listed in the imprint.

+ + +
+
+ +
+ +
+ + + + diff --git a/site/robots.txt b/site/robots.txt new file mode 100644 index 0000000..2ee8074 --- /dev/null +++ b/site/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://webexpress-framework.github.io/WebExpress/sitemap.xml diff --git a/site/sitemap.xml b/site/sitemap.xml new file mode 100644 index 0000000..98a9c91 --- /dev/null +++ b/site/sitemap.xml @@ -0,0 +1,18 @@ + + + + https://webexpress-framework.github.io/WebExpress/ + monthly + 1.0 + + + https://webexpress-framework.github.io/WebExpress/privacy.html + yearly + 0.3 + + + https://webexpress-framework.github.io/WebExpress/imprint.html + yearly + 0.3 + + From d908aa3e2691d683b3dca16f4e64efcbb8de2b8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 10:09:33 +0000 Subject: [PATCH 7/7] fix review comments on landing page docs Agent-Logs-Url: https://github.com/webexpress-framework/WebExpress/sessions/c73b8b0d-3fcc-4a17-a830-f5c49098af80 Co-authored-by: ReneSchwarzer <31061438+ReneSchwarzer@users.noreply.github.com> --- .github/workflows/generate-docs.yml | 82 +++++++++++++++++++++++++---- README.md | 4 +- site/404.html | 6 +-- site/README.md | 4 +- site/imprint.html | 2 +- 5 files changed, 81 insertions(+), 17 deletions(-) diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml index 85db9c5..bb8c9a2 100644 --- a/.github/workflows/generate-docs.yml +++ b/.github/workflows/generate-docs.yml @@ -5,9 +5,6 @@ on: branches: [main] paths: - 'site/**' - - 'README.md' - - 'CONTRIBUTING.md' - - 'docs/**' - '.github/workflows/generate-docs.yml' workflow_dispatch: @@ -44,12 +41,79 @@ jobs: - name: Validate (no external resources) run: | set -euo pipefail - # fail the build if any HTML file references third-party origins, which would - # break GDPR compliance and the "no external requests" guarantee - if grep -RInE 'https?://(cdn\.|fonts\.googleapis\.com|fonts\.gstatic\.com|www\.google-analytics\.com|googletagmanager\.com)' _site --include='*.html'; then - echo "ERROR: external resource reference found — landing page must be self-contained." >&2 - exit 1 - fi + python - <<'PY' + from html.parser import HTMLParser + from pathlib import Path + + RESOURCE_ATTRIBUTES = { + ("script", "src"), + ("img", "src"), + ("img", "srcset"), + ("iframe", "src"), + ("audio", "src"), + ("video", "src"), + ("track", "src"), + ("source", "src"), + ("source", "srcset"), + ("embed", "src"), + ("object", "data"), + } + + LINK_RESOURCE_RELS = { + "stylesheet", + "icon", + "apple-touch-icon", + "manifest", + "preload", + "modulepreload", + "prefetch", + "dns-prefetch", + "preconnect", + "mask-icon", + "shortcut icon", + } + + def external_values(raw_value: str): + for candidate in raw_value.split(","): + value = candidate.strip().split(" ", 1)[0] + if value.startswith(("http://", "https://", "//")): + yield value + + class ResourceParser(HTMLParser): + def __init__(self, path: Path): + super().__init__() + self.path = path + self.violations = [] + + def handle_starttag(self, tag, attrs): + attr_map = dict(attrs) + rel_tokens = set(attr_map.get("rel", "").lower().split()) + + for name, value in attrs: + if not value: + continue + + if (tag, name) in RESOURCE_ATTRIBUTES: + for external in external_values(value): + self.violations.append((tag, name, external)) + + if tag == "link" and name == "href": + rel_value = attr_map.get("rel", "").lower().strip() + if rel_tokens & LINK_RESOURCE_RELS or rel_value in LINK_RESOURCE_RELS: + for external in external_values(value): + self.violations.append((tag, name, external)) + + violations = [] + for path in Path("_site").rglob("*.html"): + parser = ResourceParser(path) + parser.feed(path.read_text(encoding="utf-8")) + violations.extend((path, *violation) for violation in parser.violations) + + if violations: + for path, tag, attr, value in violations: + print(f"{path}: external resource via <{tag} {attr}=\"{value}\">", flush=True) + raise SystemExit("ERROR: external resource reference found — landing page must be self-contained.") + PY - name: Upload Pages artifact uses: actions/upload-pages-artifact@v3 diff --git a/README.md b/README.md index 283afea..a9fa417 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![WebExpress-Framework](https://raw.githubusercontent.com/webexpress-framework/.github/main/docs/assets/img/banner.png) # WebExpress -WebExpress is a lightweight web server optimized for use in low-performance environments (e.g., Rasperry PI). By providing a powerful plugin system and a comprehensive API, web applications can be easily and quickly integrated into a .net language (e.g., C#). Some advantages of WebExpress are: +WebExpress is a lightweight web server optimized for use in low-performance environments (e.g., Raspberry Pi). By providing a powerful plugin system and a comprehensive API, web applications can be easily and quickly integrated into a .NET language (e.g., C#). Some advantages of WebExpress are: - It is easy to use. - It offers a variety of features and tools that can help you build and manage your website. @@ -44,4 +44,4 @@ Begin with our basic tutorial: - [WebIndex](https://github.com/webexpress-framework/WebExpress.Tutorial.WebIndex#readme) # Tags -#WebExpress #WebServer \ No newline at end of file +#WebExpress #WebServer diff --git a/site/404.html b/site/404.html index d1ed7b7..83a26ef 100644 --- a/site/404.html +++ b/site/404.html @@ -7,8 +7,8 @@ Page not found — WebExpress - - + +
@@ -17,7 +17,7 @@

This page is not part of the WebExpress site.

The URL you requested does not exist here. From the home page you can reach the documentation, the contribution guide and the project on GitHub.

- Back to home + Back to home View on GitHub

diff --git a/site/README.md b/site/README.md index 66b052f..f2b0ece 100644 --- a/site/README.md +++ b/site/README.md @@ -20,8 +20,8 @@ This folder is the source of the static landing page that is deployed to GitHub - System fonts only. - Responsive, mobile-first, WCAG AA in mind. -The workflow validates that no third-party origins are referenced from any -HTML file before it deploys to Pages. +The workflow validates that HTML files do not load third-party resources +automatically before it deploys to Pages. Outbound links are allowed. ## Local preview diff --git a/site/imprint.html b/site/imprint.html index eb936a9..a6feb17 100644 --- a/site/imprint.html +++ b/site/imprint.html @@ -42,7 +42,7 @@

Responsible for the content

Maintainer: René Schwarzer
Email: webexpress-framework@outlook.com
Project page: github.com/webexpress-framework/WebExpress
- Postal address: Available upon request where a legitimate interest is demonstrated (in accordance with GDPR Art. 6(1)(f))] + Postal address: Available upon request where a legitimate interest is demonstrated (in accordance with GDPR Art. 6(1)(f))

Hosting