Resolve "Documetation website" #69

Merged
leon.hoppe merged 4 commits from feature/docs into dev 2025-02-13 19:24:31 +01:00
33 changed files with 1392 additions and 13 deletions

View File

@@ -4,6 +4,7 @@ stages:
- build
- test
- publish
- publish-help
before_script:
- echo "Setting up environment"
@@ -37,3 +38,16 @@ publish:
dependencies:
- build
- test
publish-help:
stage: publish-help
script:
- export VERSION=$(echo $CI_COMMIT_TAG | sed 's/^v//')
- docker login -u leon.hoppe -p ${CI_REGISTRY_PASSWORD} registry.leon-hoppe.de
- docker build -t registry.leon-hoppe.de/leon.hoppe/hopframe:$VERSION -t registry.leon-hoppe.de/leon.hoppe/hopframe:latest .
- docker push registry.leon-hoppe.de/leon.hoppe/hopframe:$VERSION
- docker push registry.leon-hoppe.de/leon.hoppe/hopframe:latest
only:
- tags
dependencies:
- publish

View File

@@ -12,11 +12,10 @@
</component>
<component name="ChangeListManager">
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/TestPlugin.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/SearchEvent.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/Events/SearchEvent.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/docs/Dockerfile" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docs/Writerside/hopframe.tree" beforeDir="false" afterPath="$PROJECT_DIR$/docs/Writerside/hopframe.tree" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docs/Writerside/topics/starter.md" beforeDir="false" afterPath="$PROJECT_DIR$/docs/Writerside/topics/Overview.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/HopFrameConfiguratorExtensions.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/HopFrameConfiguratorExtensions.cs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -99,7 +98,7 @@
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ff37d54b3bf4d2756237fb789635831532603376e940f63d634b869d26d74c/Regular16.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/src/HopFrame.Core/Config/DbContextConfig.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="mock://C:/Users/leon/Documents/Projekte/HopFrame/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor" root0="SKIP_HIGHLIGHTING" root1="FORCE_HIGHLIGHTING" root2="FORCE_HIGHLIGHTING" />
<setting file="mock://C:/Users/leon/Documents/Projekte/HopFrame/src/HopFrame.Web/Plugins/Internal/PluginOrchestrator.cs" root0="SKIP_HIGHLIGHTING" />
<setting file="mock://C:/Users/leon/Documents/Projekte/HopFrame/src/HopFrame.Web/Plugins/Events/SearchEvent.cs" root0="SKIP_HIGHLIGHTING" />
</component>
<component name="KubernetesApiPersistence">{}</component>
<component name="KubernetesApiProvider">{
@@ -129,14 +128,14 @@
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;b5f11219-dfc4-47a1-b02c-90ab603034fb.executor&quot;: &quot;Debug&quot;,
&quot;dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor&quot;: &quot;Debug&quot;,
&quot;git-widget-placeholder&quot;: &quot;dev&quot;,
&quot;git-widget-placeholder&quot;: &quot;!31 on feature/docs&quot;,
&quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.environmentSetup&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</component>
@@ -244,7 +243,11 @@
<workItem from="1738422949337" duration="141000" />
<workItem from="1738512801911" duration="6776000" />
<workItem from="1738769458367" duration="5256000" />
<workItem from="1738774834563" duration="361000" />
<workItem from="1738774834563" duration="728000" />
<workItem from="1739301922710" duration="33000" />
<workItem from="1739352479748" duration="3047000" />
<workItem from="1739369355001" duration="1751000" />
<workItem from="1739461452173" duration="5533000" />
</task>
<task id="LOCAL-00001" summary="Added basic configuration">
<option name="closed" value="true" />
@@ -566,7 +569,15 @@
<option name="project" value="LOCAL" />
<updated>1738774569657</updated>
</task>
<option name="localTasksCounter" value="41" />
<task id="LOCAL-00041" summary="Added custom search functionality">
<option name="closed" value="true" />
<created>1738775556256</created>
<option name="number" value="00041" />
<option name="presentableId" value="LOCAL-00041" />
<option name="project" value="LOCAL" />
<updated>1738775556256</updated>
</task>
<option name="localTasksCounter" value="42" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -617,7 +628,6 @@
<component name="UnityProjectConfiguration" hasMinimizedUI="false" />
<component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
<MESSAGE value="Added documentation for the configurators and service extensions methods" />
<MESSAGE value="Created tests for the core module" />
<MESSAGE value="Added more tests" />
<MESSAGE value="Added web module tests" />
@@ -642,6 +652,7 @@
<MESSAGE value="Passed cancellation tokens to event handlers if needed" />
<MESSAGE value="Added plugin buttons" />
<MESSAGE value="Added default button removal feature" />
<option name="LAST_COMMIT_MESSAGE" value="Added default button removal feature" />
<MESSAGE value="Added custom search functionality" />
<option name="LAST_COMMIT_MESSAGE" value="Added custom search functionality" />
</component>
</project>

3
docs/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

8
docs/.idea/docs.iml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
docs/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/docs.iml" filepath="$PROJECT_DIR$/.idea/docs.iml" />
</modules>
</component>
</project>

6
docs/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

19
docs/Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM jetbrains/writerside-builder:243.22562 AS build
ARG INSTANCE=Writerside/hopframe
RUN mkdir /opt/sources
WORKDIR /opt/sources
ADD Writerside ./Writerside
RUN export DISPLAY=:99 && Xvfb :99 & /opt/builder/bin/idea.sh helpbuilderinspect --source-dir /opt/sources --product $INSTANCE --runner other --output-dir /opt/wrs-output/
WORKDIR /opt/wrs-output
RUN unzip -O UTF-8 webHelpHOPFRAME2-all.zip -d /opt/wrs-output/unzipped-artifact
FROM httpd:2.4 AS http-server
COPY --from=build /opt/wrs-output/unzipped-artifact/ /usr/local/apache2/htdocs/

6
docs/Writerside/c.list Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE categories
SYSTEM "https://resources.jetbrains.com/writerside/1.0/categories.dtd">
<categories>
<category id="wrs" name="Writerside documentation" order="1"/>
</categories>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE buildprofiles SYSTEM "https://resources.jetbrains.com/writerside/1.0/build-profiles.dtd">
<buildprofiles xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/build-profiles.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<build-profile instance="hopframe">
<sitemap priority="0.35" change-frequency="monthly"/>
<variables>
<noindex-content>false</noindex-content>
</variables>
</build-profile>
</buildprofiles>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE terms SYSTEM "https://resources.jetbrains.com/writerside/1.0/glossary.dtd">
<terms>
<term name="foo">
Description of what "foo" is.
</term>
</terms>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE instance-profile
SYSTEM "https://resources.jetbrains.com/writerside/1.0/product-profile.dtd">
<instance-profile id="hopframe"
name="HopFrame"
start-page="Overview.md">
<toc-element topic="Overview.md"/>
<toc-element topic="Installation.md"/>
<toc-element topic="Authentication.md"/>
<toc-element toc-title="Core Module">
<toc-element toc-title="Configurations">
<toc-element topic="HopFrameConfig.md"/>
<toc-element topic="DbContextConfig.md"/>
<toc-element topic="TableConfig.md"/>
<toc-element topic="PropertyConfig.md"/>
</toc-element>
<toc-element topic="Callbacks.md"/>
</toc-element>
<toc-element toc-title="Web Module">
<toc-element toc-title="Interface">
<toc-element topic="Custom-Views.md"/>
<toc-element topic="Table.md"/>
<toc-element topic="Dashboard.md"/>
</toc-element>
<toc-element topic="Plugins.md">
<toc-element topic="Events.md">
</toc-element>
</toc-element>
</toc-element>
</instance-profile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rules SYSTEM "https://resources.jetbrains.com/writerside/1.0/redirection-rules.dtd">
<rules>
<!-- format is as follows
<rule id="<unique id>">
<accepts>page.html</accepts>
</rule>
-->
</rules>

View File

@@ -0,0 +1,35 @@
# Authentication
The HopFrame is a powerful tool to manage your backend data. So you probably don't want anybody to access the pages.
Luckily the HopFrame supports policy based authentication. By default, everybody is allowed to access the whole
HopFrame, but you can restrict that by registering a scoped service implementing the `IHopFrameAuthHandler`.
If no service is registered, the default handler gets registered, but it lets any traffic pass.
## Example
Create a service that handles authentication:
```C#
public class AuthService(IAuthStore store) : IHopFrameAuthHandler {
public async Task<bool> IsAuthenticatedAsync(string? policy) {
var currentUser = await store.GetCurrentUser();
return await store.IsPermitted(currentUser, policy);
}
public async Task<string> GetCurrentUserDisplayNameAsync() {
var currentUser = await store.GetCurrentUser();
return currentUser.FullName;
}
}
```
Now register it in the DI container:
```C#
builder.Services.AddScoped<IHopFrameAuthHandler, AuthService>();
```
**Hint:** You can display the current users name in the ui by enabling the feature in
the [HopFrameConfig](HopFrameConfig.md#displayuserinfo).

View File

@@ -0,0 +1,31 @@
# Callbacks
Callbacks are a way of executing actions on curtain events in the web ui.
## Registering a callback handler
You can register a callback handler using the method provided in the [](TableConfig.md):
```c#
table.AddCallbackHandler(CallbackType.DeleteEntry, (user, services) => {
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("User {user} deleted!", user.Username);
});
```
The callback handler takes the entity that's modified and a `IServiceProvider` as arguments
and can either be synchronous or asynchronous.
## Callback types
```C#
public enum CallbackType {
CreateEntry = 0,
UpdateEntry = 1,
DeleteEntry = 2
}
```
- `CallbackType.CreateEntry`: The handler gets executed, when an entity is created.
- `CallbackType.UpdateEntry`: The handler gets executed, when an entity is updated.
- `CallbackType.DeleteEntry`: The handler gets executed, when an entity is deleted.

View File

@@ -0,0 +1,101 @@
# Custom Views
You can also add your own pages to the HopFrame UI by defining the routes as custom views.
You can do that by using the following extension methods for the [](HopFrameConfig.md):
## Configuration methods
### AddCustomView (With configurator delegate)
Creates an entry to the side menu and dashboard with a custom URL.
```c#
HopFrameConfigurator AddCustomView(string name, string url, Action<CustomViewConfigurator> configuratorDelegate)
```
- **Parameters:**
- `configurator`: The configurator for the HopFrame config that is being created.
- `name`: The name of the navigation entry.
- `url`: The target URL of the navigation entry.
- `configuratorDelegate`: The delegate for configuring the view.
- **Returns:** `HopFrameConfigurator`
### AddCustomView (Without configurator delegate)
Creates an entry to the side menu and dashboard with a custom URL.
```c#
CustomViewConfigurator AddCustomView(string name, string url)
```
- **Parameters:**
- `configurator`: The configurator for the HopFrame config that is being created.
- `name`: The name of the navigation entry.
- `url`: The target URL of the navigation entry.
- **Returns:** `CustomViewConfigurator`
## CustomViewConfigurator
### SetDescription
Sets the description displayed in the dashboard.
```c#
CustomViewConfigurator SetDescription(string description)
```
- **Parameters:**
- `description`: The desired description.
- **Returns:** `CustomViewConfigurator`
### SetPolicy
Sets the policy needed in order to access the view.
```c#
CustomViewConfigurator SetPolicy(string policy)
```
- **Parameters:**
- `policy`: The desired policy.
- **Returns:** `CustomViewConfigurator`
### SetIcon
Sets the icon displayed in the sidebar.
```c#
CustomViewConfigurator SetIcon(string icon)
```
- **Parameters:**
- `icon`: The desired [fluent-icon](https://www.fluentui-blazor.net/Icon#explorer).
- **Returns:** `CustomViewConfigurator`
### SetLinkMatch
Sets the rule for the sidebar to determine if the link is active.
```c#
CustomViewConfigurator SetLinkMatch(NavLinkMatch match)
```
- **Parameters:**
- `match`: The desired match rule.
- **Returns:** `CustomViewConfigurator`
## Example
```C#
builder.Services.AddHopFrame(options => {
options.AddCustomView("Counter", "/counter")
.SetDescription("A custom view")
.SetPolicy("counter.view");
});
```

View File

@@ -0,0 +1,9 @@
# Dashboard
The dashboard gives you an overview of all pages accessible through the HopFrame interface.
An example configuration could lead to something like this:
![dashboard.png](dashboard.png)
You could use the sidebar or the `Open` button to open any page that you have access to.

View File

@@ -0,0 +1,38 @@
# DbContextConfig
This config contains all configurations for the given DbContext type.
## Configuration methods
### Table (With configurator)
Configures the table of the `DbContext` using the provided configurator.
```c#
DbContextConfigurator<TDbContext> Table<TModel>(Action<TableConfigurator<TModel>> configurator) where TModel : class
```
- **Type Parameters:**
- `TModel`: The model of the table for identifying the correct one.
- **Parameters:**
- `configurator`: Used for configuring the table.
- **Returns:** `DbContextConfigurator<TDbContext>`
- **See Also:** [](TableConfig.md)
### Table (Without configurator)
Configures the table of the `DbContext`.
```c#
TableConfigurator<TModel> Table<TModel>() where TModel : class
```
- **Type Parameters:**
- `TModel`: The model of the table for identifying the correct one.
- **Returns:** `TableConfigurator<TModel>`
- **See Also:** [](TableConfig.md)

View File

@@ -0,0 +1,214 @@
# Events
## Base event
Every event inherits from the base event, so these properties and methods are always available
```C#
public abstract class HopFrameEventArgs {
public TSender Sender { get; }
public bool IsCanceled { get; }
public TableConfig Table { get; }
public void SetCancelled(bool canceled);
}
```
**Properties:**
- **Sender**: The sender of the event.
- **Type:** `TSender`
- **IsCanceled**: Indicates whether the event is canceled.
- **Type:** `bool`
- **Table**: The table configuration related to the event.
- **Type:** `TableConfig`
**Methods:**
- **SetCancelled**
- **Parameters:**
- `canceled`: A boolean value to set the cancellation state.
- **Returns:** `void`
## DeleteEntryEvent
Event arguments for a delete entry event.
```C#
public sealed class DeleteEntryEvent : HopFrameEventArgs {
public object Entity { get; }
}
```
**Properties:**
- **Entity**: The entity being deleted.
- **Type:** `object`
## CreateEntryEvent
Event arguments for a create entry event.
```C#
public sealed class CreateEntryEvent : HopFrameEventArgs {
}
```
## UpdateEntryEvent
Event arguments for an update entry event.
```C#
public sealed class UpdateEntryEvent : HopFrameEventArgs {
public object Entity { get; }
}
```
**Properties:**
- **Entity**: The entity being updated.
- **Type:** `object`
## SelectEntryEvent
Event arguments for a select entry event.
```C#
public sealed class SelectEntryEvent : HopFrameEventArgs {
public object Entity { get; }
public bool Selected { get; set; }
}
```
**Properties:**
- **Entity**: The entity being selected.
- **Type:** `object`
- **Selected**: Indicates whether the entity is selected.
- **Type:** `bool`
## PageChangeEvent
Event arguments for a page change event.
```C#
public sealed class PageChangeEvent : HopFrameEventArgs {
public int CurrentPage { get; }
public int TotalPages { get; }
public int NewPage { get; set; }
}
```
**Properties:**
- **CurrentPage**: The current page number.
- **Type:** `int`
- **TotalPages**: The total number of pages.
- **Type:** `int`
- **NewPage**: The new page number to navigate to.
- **Type:** `int`
## ReloadEvent
Event arguments for a reload event.
```C#
public sealed class ReloadEvent : HopFrameEventArgs {
}
```
## SearchEvent
Event arguments for a search event.
```C#
public sealed class SearchEvent : HopFrameEventArgs {
public string SearchTerm { get; set; }
public int CurrentPage { get; }
public void SetSearchResult(IEnumerable result, int totalPages);
}
```
**Properties:**
- **SearchTerm**: The search term used.
- **Type:** `string`
- **CurrentPage**: The current page number.
- **Type:** `int`
- **SearchResult**: The search results.
- **Type:** `IEnumerable<object>?`
- **TotalPages**: The total number of pages of search results.
- **Type:** `int`
**Methods:**
- **SetSearchResult**
- **Parameters:**
- `result`: The current page of search results.
- `totalPages`: The total pages of search results.
- **Returns:** `void`
## TableInitializedEvent
Event arguments for a table initialization event.
```C#
public class TableInitializedEvent : HopFrameEventArgs {
public List<PluginButton> PluginButtons { get; };
public DefaultButtonToggles DefaultButtons { get; set; };
public void AddPageButton(string title, Func<Task> callback, bool pushRight = false, IconInfo? icon = null);
public void AddPageButton(string title, Action callback, bool pushRight = false, IconInfo? icon = null);
public void AddPageButton<TEntity>(string title, Func<Task> callback, bool pushRight = false, IconInfo? icon = null);
public void AddPageButton<TEntity>(string title, Action callback, bool pushRight = false, IconInfo? icon = null);
public void AddEntityButton(IconInfo icon, Func<object, TableConfig, Task> callback);
public void AddEntityButton(IconInfo icon, Action<object, TableConfig> callback);
public void AddEntityButton<TEntity>(IconInfo icon, Func<TEntity, TableConfig, Task> callback);
public void AddEntityButton<TEntity>(IconInfo icon, Action<TEntity, TableConfig> callback);
}
```
**Properties:**
- **PluginButtons**: The list of plugin buttons for the table.
- **Type:** `List<PluginButton>`
- **DefaultButtons**: The default button toggles for the table.
- **Type:** `DefaultButtonToggles`
**Methods:**
- **AddPageButton**
- **Parameters:**
- `title`: The title of the button.
- `callback`: The callback function for the button.
- `pushRight`: Indicates whether to push the button to the right. (default: `false`)
- `icon`: The icon for the button. (default: `null`)
- **Returns:** `void`
- **AddEntityButton**
- **Parameters:**
- `icon`: The icon for the button.
- `callback`: The callback function for the button.
- **Returns:** `void`
## ValidationEvent
Event arguments for a validation event.
```C#
public sealed class ValidationEvent : HopFrameEventArgs {
public IList<string> Errors { get; }
public PropertyConfig Property { get; }
}
```
**Properties:**
- **Errors**: The list of validation errors.
- **Type:** `IList<string>`
- **Property**: The property being validated.
- **Type:** `PropertyConfig`

View File

@@ -0,0 +1,159 @@
# HopFrameConfig
The HopFrame config is the global object containing all configurations made for the HopFrame.
It is registered as a singleton and can be injected by any service.
But it should be treated as a read only dependency because changing the configuration during runtime is not tested and may cause bugs.
## Changing the configuration
As already mentioned in the [](Installation.md), you configure the HopFrame using the extension method of the `IServiceCollection` named `AddHopFrame`.
This extension method eiter takes a `HopFrameConfig` or a configurator action with a `HopFrameConfigurator` as an argument.
You can optionally also provide a `LibraryConfiguration` for the Fluent UI library and a toggle named `addRazorComponents` which disables the calls for adding
Razor pages with interactive server components if you want to do this yourself.
### Mapping the HopFrame pages
In order for the HopFrame pages to be served you need to add them to your application.
You can do that in two ways:
1. Just use the HopFrame as the razor host (Useful for APIs)
Just map the HopFrame before you run your application:
```c#
app.MapHopFrame();
```
2. Add the HopFrame to your Razor container (Useful for Blazor web apps)
Add the HopFrame to the `MapRazorComponents` call:
```C#
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddHopFramePages();
```
### Example
```C#
builder.Services.AddHopFrame(options => {
options.DisplayUserInfo(false);
options.AddDbContext<DatabaseContext>(context => {
context.Table<User>(table => {
table.Property(u => u.Password)
.DisplayValue(false);
table.Property(u => u.Id)
.IsSortable(false)
.SetOrderIndex(3);
table.SetViewPolicy("policy");
table.Property(u => u.Posts)
.FormatEach<Post>((post, _) => post.Caption);
});
});
options.AddCustomView("Counter", "/counter")
.SetDescription("A custom view")
.SetPolicy("counter.view");
});
```
## Configuration methods
### AddDbContext (With configurator)
Adds all tables defined in the `DbContext` to the HopFrame UI and configures it using the provided configurator.
```c#
HopFrameConfigurator AddDbContext<TDbContext>(Action<DbContextConfigurator<TDbContext>> configurator) where TDbContext : DbContext
```
- **Type Parameters:**
- `TDbContext`: The `DbContext` from which all tables should be added.
- **Parameters:**
- `configurator`: Used for configuring the `DbContext`.
- **Returns:** `HopFrameConfigurator`
- **See Also:** [](DbContextConfig.md)
### AddDbContext (without configurator)
Adds all tables defined in the `DbContext` to the HopFrame UI and configures it.
```c#
DbContextConfigurator<TDbContext> AddDbContext<TDbContext>() where TDbContext : DbContext
```
- **Type Parameters:**
- `TDbContext`: The `DbContext` from which all tables should be added.
- **Returns:** `DbContextConfigurator<TDbContext>`
- **See Also:** [](DbContextConfig.md)
### HasDbContext
Checks if a context is already registered in the HopFrame.
```c#
bool HasDbContext<TDbContext>() where TDbContext : DbContext
```
- **Type Parameters:**
- `TDbContext`: The context that should be checked.
- **Returns:** `true` if the context is already registered, `false` if not.
### GetDbContext
Returns a configurator for the context if it was already defined.
```c#
DbContextConfigurator<TDbContext>? GetDbContext<TDbContext>() where TDbContext : DbContext
```
- **Type Parameters:**
- `TDbContext`
- **Returns:** The configurator of the context if it already was defined, `null` if not.
### DisplayUserInfo
Determines if the name of the currently logged-in user should be displayed in the top right corner of the admin UI.
```c#
HopFrameConfigurator DisplayUserInfo(bool display)
```
- **Parameters:**
- `display`: A boolean value to set if the user info should be displayed.
- **Returns:** `HopFrameConfigurator`
### SetBasePolicy
Sets a default policy that every user needs to have in order to access the admin UI.
```c#
HopFrameConfigurator SetBasePolicy(string basePolicy)
```
- **Parameters:**
- `basePolicy`: The default policy string.
- **Returns:** `HopFrameConfigurator`
### SetLoginPage
Sets a custom login page to redirect to if the request to the admin UI was unauthorized.
```c#
HopFrameConfigurator SetLoginPage(string url)
```
- **Parameters:**
- `url`: The URL of the custom login page.
- **Returns:** `HopFrameConfigurator`

View File

@@ -0,0 +1,31 @@
# Installation
Install the nuget package using the CLI or the UI of your IDE:
```bash
dotnet add package HopFrame.Web
```
## Minimal configuration
Configuring HopFrame is straightforward and flexible. You can easily define your contexts, tables, and their properties using the provided configurators.
Simply use your editors intelli-sense to find out what you can configure.
```c#
builder.Services.AddHopFrame(options => {
options.AddDbContext<DatabaseContext>();
});
```
Then you need to map the frontend pages in your application:
```c#
app.MapHopFrame();
```
## Usage
- Navigate to `/admin` to access the admin dashboard and start managing your tables.
- Use the side menu to switch between different tables.
- Utilize the built-in CRUD functionality to manage your data seamlessly.

View File

@@ -0,0 +1,14 @@
# Overview
Welcome to the HopFrame! This project aims to provide a comprehensive and modular framework for easy management of your database.
The framework is designed to be highly configurable, ensuring that developers either quickly add the framework for simple data editing or
configure it to their needs to implement it fully in their data management pipeline.
## Features
- **Dynamic Table Management**: Create, edit, and delete records dynamically with support for various data types including numeric, text, boolean, dates, and relational data.
- **Role-Based Access Control (RBAC)**: Implement fine-grained access control policies for viewing, creating, updating, and deleting records.
- **Modern Design**: A modern and user-friendly interface built with Fluent UI components, ensuring easy to use and pleasing administration pages.
- **Validation and Error Handling**: Comprehensive input validation and error handling to ensure data integrity and provide feedback to users.
- **Support for Complex Data Relationships**: Manage complex relationships between data entities with ease.

View File

@@ -0,0 +1,62 @@
# Plugins
If the default functionality of the HopFrame does not fit your needs, you can easily extend the pages
by using Plugins. They are registered as scoped services so you can use DI like everywhere else.
## Add a plugin
Create a class that extends the `HopFramePlugin` class:
```C#
public class SearchExtension : HopFramePlugin {
}
```
Then add the plugin to the HopFrame by using the extension method on the [](HopFrameConfig.md):
```C#
builder.Services.AddHopFrame(options => {
options.AddPlugin<SearchExtension>();
});
```
## Configuring the plugin
If you want to change the HopFrame configuration from within your plugin, you can create a static method
and decorate it with the `PluginConfigurator` attribute. Here you can inject the `HopFrameConfigurator`
as an argument and change the configuration. Keep in mind, that this function automatically gets called
when you register your plugin, so any changes after that override the changes made in the plugin.
### Example
```C#
[PluginConfigurator]
public static void Configure(HopFrameConfigurator configurator) {
configurator.AddCustomView("Counter", "/counter")
.SetDescription("A custom view")
.SetPolicy("counter.view");
}
```
## Events
The HopFrame provides various [events](Events.md) that can change how the corresponding action behaves. You can register
event handlers similar to the [configurator method](#configuring-the-plugin). Create a method, that is **not** static
and decorate it with the `EventHandler` attribute. This method can return either `void` or a `Task`. Then declare the
Event type as an argument and the function gets automatically registered as an event handler for the corresponding event.
### Examples
```C#
[EventHandler]
public async Task OnSearch(SearchEvent e) {
var result = await searchHandler.Search(e.Table, e.SearchTerm);
e.SetSearchResult(result.Items, result.TotalPages);
}
[EventHandler]
public void OnDelete(DeleteEntryEvent e) {
cacheHandler.ClearCache(e.Entity);
}
```

View File

@@ -0,0 +1,286 @@
# PropertyConfig
This configuration contains all configurations for the given property type on the table.
## Configuration methods
### SetDisplayName
Sets the title displayed in the table header and edit dialog.
```c#
PropertyConfigurator<TProp> SetDisplayName(string displayName)
```
- **Parameters:**
- `displayName`: The display name for the property.
- **Returns:** `PropertyConfigurator<TProp>`
### List
Determines if the property should appear in the table, if not the property is also set to be not searchable.
```c#
PropertyConfigurator<TProp> List(bool list)
```
- **Parameters:**
- `list`: A boolean value to set if the property should appear in the table.
- **Returns:** `PropertyConfigurator<TProp>`
### IsSortable
Determines if the table can be sorted by the property.
```c#
PropertyConfigurator<TProp> IsSortable(bool sortable)
```
- **Parameters:**
- `sortable`: A boolean value to set if the property is sortable.
- **Returns:** `PropertyConfigurator<TProp>`
### IsSearchable
Determines if the property get taken into account for search results.
```c#
PropertyConfigurator<TProp> IsSearchable(bool searchable)
```
- **Parameters:**
- `searchable`: A boolean value to set if the property is searchable.
- **Returns:** `PropertyConfigurator<TProp>`
### SetDisplayedProperty
Determines if the value that should be displayed instead of the string representation of the type.
```c#
PropertyConfigurator<TProp> SetDisplayedProperty<TInnerProp>(Expression<Func<TProp, TInnerProp>> propertyExpression)
```
- **Type Parameters:**
- `TInnerProp`: The inner property type to display.
- **Parameters:**
- `propertyExpression`: The expression to determine the property.
- **Returns:** `PropertyConfigurator<TProp>`
### Format (Synchronous)
Determines the value that's displayed in the admin UI.
```c#
PropertyConfigurator<TProp> Format(Func<TProp, IServiceProvider, string> formatter)
```
- **Parameters:**
- `formatter`: The function to format the value.
- **Returns:** `PropertyConfigurator<TProp>`
- **See Also:** [](#setdisplayedproperty)
### Format (Asynchronous)
Determines the value that's displayed in the admin UI.
```c#
PropertyConfigurator<TProp> Format(Func<TProp, IServiceProvider, Task<string>> formatter)
```
- **Parameters:**
- `formatter`: The function to format the value.
- **Returns:** `PropertyConfigurator<TProp>`
### FormatEach (Synchronous)
Determines the value that's displayed for each entry in the list.
```c#
PropertyConfigurator<TProp> FormatEach<TInnerProp>(Func<TInnerProp, IServiceProvider, string> formatter)
```
- **Parameters:**
- `formatter`: The function to format the value for each entry.
- **Returns:** `PropertyConfigurator<TProp>`
### FormatEach (Asynchronous)
Determines the value that's displayed for each entry in the list.
```c#
PropertyConfigurator<TProp> FormatEach<TInnerProp>(Func<TInnerProp, IServiceProvider, Task<string>> formatter)
```
- **Parameters:**
- `formatter`: The function to format the value for each entry.
- **Returns:** `PropertyConfigurator<TProp>`
### SetParser (Synchronous)
Determines the function used for parsing the value provided in the editor dialog to the actual property value.
```c#
PropertyConfigurator<TProp> SetParser(Func<string, IServiceProvider, TProp> parser)
```
- **Parameters:**
- `parser`: The function to parse the value.
- **Returns:** `PropertyConfigurator<TProp>`
### SetParser (Asynchronous)
Determines the function used for parsing the value provided in the editor dialog to the actual property value.
```c#
PropertyConfigurator<TProp> SetParser(Func<string, IServiceProvider, Task<TProp>> parser)
```
- **Parameters:**
- `parser`: The function to parse the value.
- **Returns:** `PropertyConfigurator<TProp>`
### SetEditable
Determines if the value can be edited in the admin UI. If true, the value can still be initially set, but not modified.
```c#
PropertyConfigurator<TProp> SetEditable(bool editable)
```
- **Parameters:**
- `editable`: A boolean value to set if the property is editable.
- **Returns:** `PropertyConfigurator<TProp>`
### SetCreatable
Determines if the initial value can be edited in the admin UI. If true the value will not be visible in the create dialog.
```c#
PropertyConfigurator<TProp> SetCreatable(bool creatable)
```
- **Parameters:**
- `creatable`: A boolean value to set if the property is creatable.
- **Returns:** `PropertyConfigurator<TProp>`
### DisplayValue
Determines if the value should be displayed in the admin UI (useful for secrets like passwords etc.).
```c#
PropertyConfigurator<TProp> DisplayValue(bool display)
```
- **Parameters:**
- `display`: A boolean value to set if the property value is displayed.
- **Returns:** `PropertyConfigurator<TProp>`
### IsTextArea
Determines if the admin UI should use a text area for modifying the value.
```c#
PropertyConfigurator<TProp> IsTextArea(bool textField)
```
- **Parameters:**
- `textField`: A boolean value to set if the property is a text area.
- **Returns:** `PropertyConfigurator<TProp>`
### SetTextAreaRows
Determines the initial size of the text area field.
```c#
PropertyConfigurator<TProp> SetTextAreaRows(int rows)
```
- **Parameters:**
- `rows`: The number of rows for the text area.
- **Returns:** `PropertyConfigurator<TProp>`
### SetValidator (Synchronous)
Determines the validator used for the property value before saving.
```c#
PropertyConfigurator<TProp> SetValidator(Func<TProp?, IServiceProvider, IEnumerable<string>> validator)
```
- **Parameters:**
- `validator`: The function to validate the property value.
- **Returns:** `PropertyConfigurator<TProp>`
### SetValidator (Asynchronous)
Determines the validator used for the property value before saving.
```c#
PropertyConfigurator<TProp> SetValidator(Func<TProp?, IServiceProvider, Task<IEnumerable<string>>> validator)
```
- **Parameters:**
- `validator`: The function to validate the property value.
- **Returns:** `PropertyConfigurator<TProp>`
### SetOrderIndex
Determines the order index for the property in the admin UI.
```c#
PropertyConfigurator<TProp> SetOrderIndex(int index)
```
- **Parameters:**
- `index`: The order index for the property.
- **Returns:** `PropertyConfigurator<TProp>`
- **See Also:** [](TableConfig.md#setorderindex)
### SetDisplayLength
Sets the maximum character length displayed in the admin UI (not in the editor dialog).
```c#
PropertyConfigurator<TProp> SetDisplayLength(int maxLength)
```
- **Parameters:**
- `maxLength`: The maximum length of characters to be displayed.
- **Returns:** `PropertyConfigurator<TProp>`
### ForceRelation
Forces a property to be treated as a relation.
```c#
PropertyConfigurator<TProp> ForceRelation(bool isEnumerable = false, bool isRequired = true)
```
- **Parameters:**
- `isEnumerable`: Determines if it is possible to assign multiple objects to the property.
- `isRequired`: Determines if the property is nullable.
- **Returns:** `PropertyConfigurator<TProp>`

View File

@@ -0,0 +1,41 @@
# Table
On the table page you can view, edit, delete and create entries in that table.
You can use the many configuration methods provided by the [](TableConfig.md) to modify the look
of this page.
An example configuration could look something like this:
![table.png](table.png)
Here you can use the various buttons to interact with your entities.
## Data grid
The main aspect of this site is the data grid that displays all your entries of that table.
For performance reasons this data is paginated, you can **change the page** at the bottom of the screen
using either the arrow button or the page selector. You can **sort** your entries by clicking on
the name of the column. If you don't want a column to be sortable, you can configure this in the
[PropertyConfig](PropertyConfig.md#issortable).
## Search
The search bar will search through all entities in your database, not only the ones displayed.
Simply enter a search term and the search is performed automatically. You can configure the
columns that should be searchable in the [PropertyConfig](PropertyConfig.md#issearchable)
## Edit dialog
If you click on the pen button next to one of the entries, the edit dialog will appear,
it could look something like this:
![editor.png](editor.png)
You can modify the data of the selected entity based on your [PropertyConfigs](PropertyConfig.md).
The HopFrame can handle various property types like strings, numbers, enums, relations and many more.
### Validation
The HopFrame also supports input validation. By default, a required validation is automatically applied
to every property that's not nullable. You can change the validation behavior in the
[PropertyConfig](PropertyConfig.md#setvalidator-synchronous).

View File

@@ -0,0 +1,191 @@
# TableConfig
This configuration contains all configurations for the given table type.
## Configuration methods
### Ignore
Determines if the table should be ignored in the admin UI.
```c#
TableConfigurator<TModel> Ignore(bool ignore)
```
- **Parameters:**
- `ignore`: A boolean value to set if the table should be ignored.
- **Returns:** `TableConfigurator<TModel>`
### Property (With configurator)
Configures the property of the table using the provided configurator.
```c#
TableConfigurator<TModel> Property<TProp>(Expression<Func<TModel, TProp>> propertyExpression, Action<PropertyConfigurator<TProp>> configurator)
```
- **Parameters:**
- `propertyExpression`: Used for determining the property.
- `configurator`: Used for configuring the property.
- **Returns:** `TableConfigurator<TModel>`
- **See Also:** [](PropertyConfig.md)
### Property (Without configurator)
Configures the property of the table.
```c#
PropertyConfigurator<TProp> Property<TProp>(Expression<Func<TModel, TProp>> propertyExpression)
```
- **Parameters:**
- `propertyExpression`: Used for determining the property.
- **Returns:** `PropertyConfigurator<TProp>`
- **See Also:** [](PropertyConfig.md)
### AddVirtualProperty (With configurator)
Adds a virtual property to the table view and configures it using the provided configurator (this property will not appear in the editor).
```c#
TableConfigurator<TModel> AddVirtualProperty(string name, Func<TModel, IServiceProvider, string> template, Action<PropertyConfigurator<string>> configurator)
```
- **Parameters:**
- `name`: The name of the virtual property.
- `template`: The template used for generating the property value.
- `configurator`: Used for configuring the virtual property.
- **Returns:** `TableConfigurator<TModel>`
- **See Also:** [](PropertyConfig.md)
### AddVirtualProperty (Synchronous)
Adds a virtual property to the table view (this property will not appear in the editor).
```c#
PropertyConfigurator<string> AddVirtualProperty(string name, Func<TModel, IServiceProvider, string> template)
```
- **Parameters:**
- `name`: The name of the virtual property.
- `template`: The template used for generating the property value.
- **Returns:** `PropertyConfigurator<string>`
- **See Also:** [](PropertyConfig.md)
### AddVirtualProperty (Asynchronous)
Adds a virtual property to the table view (this property will not appear in the editor).
```c#
PropertyConfigurator<string> AddVirtualProperty(string name, Func<TModel, IServiceProvider, Task<string>> template)
```
- **Parameters:**
- `name`: The name of the virtual property.
- `template`: The template used for generating the property value.
- **Returns:** `PropertyConfigurator<string>`
- **See Also:** [](PropertyConfig.md)
### SetDisplayName
Determines the name for the table used in the admin UI and URL for the table page.
```c#
TableConfigurator<TModel> SetDisplayName(string name)
```
- **Parameters:**
- `name`: The display name for the table.
- **Returns:** `TableConfigurator<TModel>`
### SetDescription
Determines the description displayed in the admin UI.
```c#
TableConfigurator<TModel> SetDescription(string description)
```
- **Parameters:**
- `description`: The description for the table.
- **Returns:** `TableConfigurator<TModel>`
### SetOrderIndex
Determines the order index for the table in the admin UI.
```c#
TableConfigurator<TModel> SetOrderIndex(int index)
```
- **Parameters:**
- `index`: The order index for the table.
- **Returns:** `TableConfigurator<TModel>`
- **See Also:** [](PropertyConfig.md#setorderindex)
### SetViewPolicy
Determines the policy needed by a user in order to view the table.
```c#
TableConfigurator<TModel> SetViewPolicy(string policy)
```
- **Parameters:**
- `policy`: The view policy string.
- **Returns:** `TableConfigurator<TModel>`
### SetUpdatePolicy
Determines the policy needed by a user in order to edit the entries.
```c#
TableConfigurator<TModel> SetUpdatePolicy(string policy)
```
- **Parameters:**
- `policy`: The update policy string.
- **Returns:** `TableConfigurator<TModel>`
### SetCreatePolicy
Determines the policy needed by a user in order to create entries.
```c#
TableConfigurator<TModel> SetCreatePolicy(string policy)
```
- **Parameters:**
- `policy`: The create policy string.
- **Returns:** `TableConfigurator<TModel>`
### SetDeletePolicy
Determines the policy needed by a user in order to delete entries.
```c#
TableConfigurator<TModel> SetDeletePolicy(string policy)
```
- **Parameters:**
- `policy`: The delete policy string.
- **Returns:** `TableConfigurator<TModel>`

5
docs/Writerside/v.list Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE vars SYSTEM "https://resources.jetbrains.com/writerside/1.0/vars.dtd">
<vars>
<var name="product" value="Writerside"/>
</vars>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ihp SYSTEM "https://resources.jetbrains.com/writerside/1.0/ihp.dtd">
<ihp version="2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/writerside-cfg.xsd">
<topics dir="topics"/>
<images dir="images" web-path="images"/>
<categories src="c.list"/>
<vars src="v.list"/>
<instance src="hopframe.tree"/>
</ihp>

View File

@@ -48,10 +48,20 @@ public class HopFrameConfigurator(HopFrameConfig config, IServiceCollection coll
return new DbContextConfigurator<TDbContext>(context);
}
/// <summary>
/// Check if a context is already registered in the HopFrame
/// </summary>
/// <typeparam name="TDbContext">The context that should be checked</typeparam>
/// <returns>true if the context is already registered, false if not</returns>
public bool HasDbContext<TDbContext>() where TDbContext : DbContext {
return InnerConfig.Contexts.Any(context => context.ContextType == typeof(TDbContext));
}
/// <summary>
/// Returns a configurator for the context if it was already defined
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
/// <returns>The configurator of the context if it already was defined, null if not</returns>
public DbContextConfigurator<TDbContext>? GetDbContext<TDbContext>() where TDbContext : DbContext {
var config = InnerConfig.Contexts
.SingleOrDefault(context => context.ContextType == typeof(TDbContext));

View File

@@ -33,6 +33,11 @@ public static class HopFrameConfiguratorExtensions {
return configurator;
}
/// <summary>
/// Registers a plugin
/// </summary>
/// <param name="configurator">The configurator for the HopFrame config that is being created</param>
/// <typeparam name="TPlugin">The plugin that should be registered</typeparam>
public static HopFrameConfigurator AddPlugin<TPlugin>(this HopFrameConfigurator configurator) where TPlugin : HopFramePlugin {
PluginOrchestrator.RegisterPlugin(configurator.ServiceCollection, typeof(TPlugin));

View File

@@ -43,7 +43,7 @@ public sealed class CustomViewConfigurator(CustomView view) {
}
/// <summary>
/// Sets the rule for sidebar to determine if the link is active
/// Sets the rule for the sidebar to determine if the link is active
/// </summary>
/// <param name="match">The desired match rule</param>
public CustomViewConfigurator SetLinkMatch(NavLinkMatch match) {