-
Welcome
-
Learn about building Web apps with ASP.NET Core.
+@*
*@
+
+
+
Calendar.
+
diff --git a/Application/Pages/Index.cshtml.cs b/Application/Pages/Index.cshtml.cs
index c43b363..e87cd2d 100644
--- a/Application/Pages/Index.cshtml.cs
+++ b/Application/Pages/Index.cshtml.cs
@@ -9,7 +9,7 @@ namespace PlanTempus.Pages
{
private readonly TelemetryClient _telemetry;
- public IndexModel(TelemetryClient telemetry, Akka.Actor.ActorSystem system)
+ public IndexModel(TelemetryClient telemetry)
{
_telemetry = telemetry;
diff --git a/Application/Pages/Shared/_Layout.cshtml b/Application/Pages/Shared/_Layout.cshtml
index 79adbab..399643e 100644
--- a/Application/Pages/Shared/_Layout.cshtml
+++ b/Application/Pages/Shared/_Layout.cshtml
@@ -9,18 +9,17 @@
-
-
-
- @RenderBody()
-
-
+
+
+ @RenderBody()
+ top
+
+
@await RenderSectionAsync("Scripts", required: false)
diff --git a/Application/Startup.cs b/Application/Startup.cs
index 9dc122a..4f1c1b4 100644
--- a/Application/Startup.cs
+++ b/Application/Startup.cs
@@ -26,7 +26,12 @@ namespace PlanTempus
//services.AddApplicationInsightsTelemetry(ConfigurationRoot["ApplicationInsights:InstrumentationKey"]);
services.AddAntiforgery(x => x.HeaderName = "X-ANTI-FORGERY-TOKEN");
- }
+ services.Configure
(options =>
+ {
+ options.ViewLocationExpanders.Add(new Application.Common.ComponentsViewLocationExpander());
+ });
+
+ }
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule(new Core.ModuleRegistry.DbPostgreSqlModule
diff --git a/Application/wwwroot/css/site.css b/Application/wwwroot/css/site.css
index f8d98fc..8b5ec89 100644
--- a/Application/wwwroot/css/site.css
+++ b/Application/wwwroot/css/site.css
@@ -1,22 +1,171 @@
-html {
- font-size: 14px;
+:root {
+ --top-height: 70px;
+ --footer-height: 50px;
+ --theme-aside-background-color: #233242;
+ --theme-footer-background-color: #233242;
}
-@media (min-width: 768px) {
- html {
- font-size: 16px;
- }
+@font-face {
+ font-family: 'rubik-regular';
+ src: url('../fonts/rubik-regular.woff2') format('woff2');
}
-.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
- box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
+@font-face {
+ font-family: 'rubik';
+ src: url('../fonts/rubik-variablefont_wght.woff2') format('woff2');
+}
+
+.theme-light {
+ --theme-aside-background-color: beige;
+ --theme-footer-background-color: beige;
}
html {
- position: relative;
- min-height: 100%;
+ font-size: 14px;
+ font-family: rubik;
+ font-weight: 100;
+}
+
+html {
+ display: grid;
+ min-height: 100%;
}
body {
- margin-bottom: 60px;
-}
\ No newline at end of file
+ display: grid;
+ margin: 0;
+}
+
+body {
+ overflow-x: hidden;
+ margin: 0;
+ color: #6a7a8c;
+ background: #f2f4f5;
+ position: relative
+}
+
+.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
+ margin-top: 0;
+ margin-bottom: .5rem;
+ font-weight: 400;
+ line-height: 1.2;
+ color: var(--bs-heading-color, inherit);
+}
+
+.h1, h1 {
+ font-size: calc(1.35rem + 1.2vw)
+}
+
+@media (min-width: 1200px) {
+ .h1, h1 {
+ font-size: 2.25rem
+ }
+}
+
+.h2, h2 {
+ font-size: calc(1.3125rem + .75vw)
+}
+
+@media (min-width: 1200px) {
+ .h2, h2 {
+ font-size: 1.875rem
+ }
+}
+
+.h3, h3 {
+ font-size: calc(1.275rem + .3vw)
+}
+
+@media (min-width: 1200px) {
+ .h3, h3 {
+ font-size: 1.5rem
+ }
+}
+
+.h4, h4 {
+ font-size: 1.125rem
+}
+
+.h5, h5 {
+ font-size: 1rem
+}
+
+.h6, h6 {
+ font-size: .875rem
+}
+
+swp-container {
+ display: grid;
+ grid-template-columns: 250px 1.3fr;
+ grid-template-rows: var(--top-height) 1000px var(--footer-height);
+ gap: 0px 0px;
+ grid-auto-flow: row;
+ grid-template-areas:
+ "aside top"
+ "aside main"
+ "aside footer";
+}
+
+aside {
+ grid-area: aside;
+ background-color: var(--theme-aside-background-color);
+ position: sticky;
+ top: 0;
+ height: 100vh;
+ display: grid;
+ grid-template-rows: var(--top-height) auto;
+}
+
+ aside aside-top {
+ background-color: var(--theme-background-color);
+ overflow: hidden;
+ justify-self: center;
+ }
+
+ aside aside-top img {
+ transform: scale(0.7);
+ }
+ aside nav {
+ margin-top: 50px;
+ }
+ aside nav ul {
+
+ display: grid;
+ margin-block-start: 0px;
+ padding-inline-start: 0px;
+ grid-auto-rows: 50px;
+ justify-items: stretch;
+ align-items: stretch;
+ }
+
+ aside nav li {
+ align-content: center;
+ color: #fff;
+ padding-inline: 12px 15px;
+ border-left: 2px solid transparent;
+ }
+
+ aside nav li:hover {
+ background: rgba(0, 0, 0, .1);
+ border-left: 2px solid #137eff;
+ }
+
+
+main {
+ grid-area: main;
+ background-color: lightgray;
+}
+
+top {
+ grid-area: top;
+ background-color: #f2f4f5;
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ box-shadow: 0 2px 4px rgb(200, 200, 200, 0,15);
+}
+
+footer {
+ grid-area: footer;
+ background-color: var(--theme-footer-background-color);
+}
diff --git a/Application/wwwroot/fonts/Rubik-Regular.woff2 b/Application/wwwroot/fonts/Rubik-Regular.woff2
new file mode 100644
index 0000000..c93a5d6
Binary files /dev/null and b/Application/wwwroot/fonts/Rubik-Regular.woff2 differ
diff --git a/Application/wwwroot/fonts/Rubik-VariableFont_wght.woff2 b/Application/wwwroot/fonts/Rubik-VariableFont_wght.woff2
new file mode 100644
index 0000000..759c55f
Binary files /dev/null and b/Application/wwwroot/fonts/Rubik-VariableFont_wght.woff2 differ
diff --git a/Application/wwwroot/images/logo-1.png b/Application/wwwroot/images/logo-1.png
new file mode 100644
index 0000000..4ddbbd1
Binary files /dev/null and b/Application/wwwroot/images/logo-1.png differ
diff --git a/Application/wwwroot/images/pt-logo1.png b/Application/wwwroot/images/pt-logo1.png
new file mode 100644
index 0000000..f7777c7
Binary files /dev/null and b/Application/wwwroot/images/pt-logo1.png differ
diff --git a/Application/wwwroot/images/pt-logo2.png b/Application/wwwroot/images/pt-logo2.png
new file mode 100644
index 0000000..462c746
Binary files /dev/null and b/Application/wwwroot/images/pt-logo2.png differ
diff --git a/Database/Class1.cs b/Database/Class1.cs
new file mode 100644
index 0000000..06741f8
--- /dev/null
+++ b/Database/Class1.cs
@@ -0,0 +1,7 @@
+namespace Database
+{
+ public class Class1
+ {
+
+ }
+}
diff --git a/Database/Database.csproj b/Database/Database.csproj
new file mode 100644
index 0000000..35e3a11
--- /dev/null
+++ b/Database/Database.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Database/Tenants/Class1.cs b/Database/Tenants/Class1.cs
new file mode 100644
index 0000000..b6e3a70
--- /dev/null
+++ b/Database/Tenants/Class1.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Database.Tenants
+{
+ internal class Class1
+ {
+ private async Task InsertInitialData(string schema)
+ {
+ // Indsæt roles
+ var insertRoles = $@"
+ INSERT INTO {schema}.roles (name) VALUES
+ ('SYSTEM_ADMIN'),
+ ('TENANT_ADMIN'),
+ ('POWER_USER'),
+ ('BASIC_USER')";
+ await _db.ExecuteAsync(insertRoles);
+
+ // Indsæt permissions
+ var insertPermissions = $@"
+ INSERT INTO {schema}.permissions (name) VALUES
+ ('OVERVIEW_VIEW'),
+ ('CALENDAR_VIEW'),
+ ('SALES_VIEW'),
+ ('CUSTOMERS_VIEW'),
+ ('POS_VIEW'),
+ ('STATISTICS_VIEW')";
+ await _db.ExecuteAsync(insertPermissions);
+
+ // Indsæt role permissions for system admin (får alle permissions)
+ var insertAdminPermissions = $@"
+ INSERT INTO {schema}.role_permissions (role_id, permission_id)
+ SELECT
+ (SELECT id FROM {schema}.roles WHERE name = 'SYSTEM_ADMIN'),
+ id
+ FROM {schema}.permissions";
+ await _db.ExecuteAsync(insertAdminPermissions);
+
+ // Indsæt navigation templates
+ var insertTemplates = $@"
+ INSERT INTO {schema}.navigation_link_templates
+ (url, permission_id, icon, default_order)
+ VALUES
+ ('/overview',
+ (SELECT id FROM {schema}.permissions WHERE name = 'OVERVIEW_VIEW'),
+ 'home', 10),
+ ('/calendar',
+ (SELECT id FROM {schema}.permissions WHERE name = 'CALENDAR_VIEW'),
+ 'calendar', 20),
+ ('/sales',
+ (SELECT id FROM {schema}.permissions WHERE name = 'SALES_VIEW'),
+ 'shopping-cart', 30),
+ ('/customers',
+ (SELECT id FROM {schema}.permissions WHERE name = 'CUSTOMERS_VIEW'),
+ 'users', 40),
+ ('/pos',
+ (SELECT id FROM {schema}.permissions WHERE name = 'POS_VIEW'),
+ 'credit-card', 50),
+ ('/statistics',
+ (SELECT id FROM {schema}.permissions WHERE name = 'STATISTICS_VIEW'),
+ 'chart-bar', 60)";
+ await _db.ExecuteAsync(insertTemplates);
+
+ // Indsæt translations
+ var insertTranslations = $@"
+ INSERT INTO {schema}.navigation_link_template_translations
+ (template_id, language, display_name)
+ VALUES
+ (1, 'da-DK', 'Overblik'),
+ (1, 'en-US', 'Overview'),
+ (2, 'da-DK', 'Kalender'),
+ (2, 'en-US', 'Calendar'),
+ (3, 'da-DK', 'Salg'),
+ (3, 'en-US', 'Sales'),
+ (4, 'da-DK', 'Kunder'),
+ (4, 'en-US', 'Customers'),
+ (5, 'da-DK', 'Kassesystem'),
+ (5, 'en-US', 'POS System'),
+ (6, 'da-DK', 'Statistik'),
+ (6, 'en-US', 'Statistics')";
+ await _db.ExecuteAsync(insertTranslations);
+ }
+
+ }
+}
diff --git a/Database/Tenants/TenantSetupService.cs b/Database/Tenants/TenantSetupService.cs
new file mode 100644
index 0000000..9c4344f
--- /dev/null
+++ b/Database/Tenants/TenantSetupService.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Insight.Database;
+namespace Database.Tenants
+{
+
+ public class TenantSetupService
+ {
+ private readonly IDbConnection _db;
+
+ public TenantSetupService(IDbConnection db)
+ {
+ _db = db;
+ }
+
+ public async Task CreateTenant(string schema)
+ {
+ if (!Regex.IsMatch(schema, "^[a-zA-Z0-9_]+$"))
+ {
+ throw new ArgumentException("Invalid schema name");
+ }
+
+ await CreateSchema(schema);
+ await CreateRolesTable(schema);
+ await CreatePermissionsTable(schema);
+ await CreateRolePermissionsTable(schema);
+ await CreateNavigationLinkTemplatesTable(schema);
+ await CreateNavigationLinkTemplateTranslationsTable(schema);
+ }
+
+ private async Task CreateSchema(string schema)
+ {
+ var sql = $"CREATE SCHEMA IF NOT EXISTS {schema}";
+ await _db.ExecuteAsync(sql);
+ }
+ private async Task CreateUser(string user, string password)
+ {
+ var sql = $"CREATE USER {user} WITH PASSWORD '{password}';";
+ await _db.ExecuteAsync(sql);
+ }
+
+ private async Task GrantSchemaRights(string schema, string user)
+ {
+ var sql = $"GRANT USAGE ON SCHEMA {schema} TO {user};";
+ await _db.ExecuteAsync(sql);
+
+ var sql1 = $"ALTER DEFAULT PRIVILEGES IN SCHEMA {schema} " +
+ $"GRANT ALL PRIVILEGES ON TABLES TO {user};";
+ await _db.ExecuteAsync(sql1);
+
+
+ var sql2 = $"GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA {schema} TO {user};";
+ await _db.ExecuteAsync(sql2);
+
+
+
+ }
+
+
+ private async Task CreateRolesTable(string schema)
+ {
+ var sql = $@"
+ CREATE TABLE IF NOT EXISTS {schema}.roles (
+ id SERIAL PRIMARY KEY,
+ name VARCHAR(100) NOT NULL UNIQUE
+ )";
+ await _db.ExecuteAsync(sql);
+ }
+
+ private async Task CreatePermissionsTable(string schema)
+ {
+ var sql = $@"
+ CREATE TABLE IF NOT EXISTS {schema}.permissions (
+ id SERIAL PRIMARY KEY,
+ name VARCHAR(100) NOT NULL UNIQUE
+ )";
+ await _db.ExecuteAsync(sql);
+ }
+
+ private async Task CreateRolePermissionsTable(string schema)
+ {
+ var sql = $@"
+ CREATE TABLE IF NOT EXISTS {schema}.role_permissions (
+ role_id INTEGER NOT NULL,
+ permission_id INTEGER NOT NULL,
+ PRIMARY KEY (role_id, permission_id),
+ FOREIGN KEY (role_id) REFERENCES {schema}.roles(id),
+ FOREIGN KEY (permission_id) REFERENCES {schema}.permissions(id)
+ )";
+ await _db.ExecuteAsync(sql);
+ }
+
+ private async Task CreateNavigationLinkTemplatesTable(string schema)
+ {
+ var sql = $@"
+ CREATE TABLE IF NOT EXISTS {schema}.navigation_link_templates (
+ id SERIAL PRIMARY KEY,
+ url VARCHAR(500) NOT NULL,
+ permission_id INTEGER NULL,
+ icon VARCHAR(100) NULL,
+ default_order INTEGER NOT NULL,
+ FOREIGN KEY (permission_id) REFERENCES {schema}.permissions(id)
+ )";
+ await _db.ExecuteAsync(sql);
+ }
+
+ private async Task CreateNavigationLinkTemplateTranslationsTable(string schema)
+ {
+ var sql = $@"
+ CREATE TABLE IF NOT EXISTS {schema}.navigation_link_template_translations (
+ id SERIAL PRIMARY KEY,
+ template_id INTEGER NOT NULL,
+ language VARCHAR(10) NOT NULL,
+ display_name VARCHAR(100) NOT NULL,
+ FOREIGN KEY (template_id) REFERENCES {schema}.navigation_link_templates(id)
+ )";
+ await _db.ExecuteAsync(sql);
+ }
+ }
+
+
+}
diff --git a/PlanTempus.sln b/PlanTempus.sln
index e1e54bc..08b21ed 100644
--- a/PlanTempus.sln
+++ b/PlanTempus.sln
@@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{111CE8AE-E637-4376-A5A3-88D33E77EA88}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Database", "Database\Database.csproj", "{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -27,6 +29,10 @@ Global
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{111CE8AE-E637-4376-A5A3-88D33E77EA88}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE