diff --git a/SS14.Admin/Helpers/BanExemptions.cs b/SS14.Admin/Helpers/BanExemptions.cs
new file mode 100644
index 0000000..5629d0d
--- /dev/null
+++ b/SS14.Admin/Helpers/BanExemptions.cs
@@ -0,0 +1,31 @@
+using Content.Server.Database;
+
+namespace SS14.Admin.Helpers;
+
+public static class BanExemptions
+{
+ public static (ServerBanExemptFlags Value, string DisplayName)[] GetExemptions()
+ {
+ return
+ [
+ (ServerBanExemptFlags.Datacenter, "Datacenter"),
+ (ServerBanExemptFlags.IP, "Only matches IP"),
+ (ServerBanExemptFlags.BlacklistedRange, "Blacklisted range"),
+ ];
+ }
+
+ public static ServerBanExemptFlags GetExemptionFromForm(IFormCollection formCollection)
+ {
+ var flags = ServerBanExemptFlags.None;
+
+ foreach (var (value, _) in GetExemptions())
+ {
+ if (formCollection.TryGetValue($"exemption_{value}", out var checkValue) && checkValue == "on")
+ {
+ flags |= value;
+ }
+ }
+
+ return flags;
+ }
+}
diff --git a/SS14.Admin/Helpers/NoteSeverityHelper.cs b/SS14.Admin/Helpers/NoteSeverityHelper.cs
new file mode 100644
index 0000000..b6a1655
--- /dev/null
+++ b/SS14.Admin/Helpers/NoteSeverityHelper.cs
@@ -0,0 +1,15 @@
+using Content.Shared.Database;
+using Microsoft.AspNetCore.Mvc.Rendering;
+
+namespace SS14.Admin.Helpers;
+
+public static class NoteSeverityHelper
+{
+ public static SelectListItem[] SeverityItems =>
+ [
+ new SelectListItem("None", NoteSeverity.None.ToString()),
+ new SelectListItem("Minor", NoteSeverity.Minor.ToString()),
+ new SelectListItem("Medium", NoteSeverity.Medium.ToString()),
+ new SelectListItem("High", NoteSeverity.High.ToString()),
+ ];
+}
diff --git a/SS14.Admin/Pages/Bans/BanTemplates/Create.cshtml b/SS14.Admin/Pages/Bans/BanTemplates/Create.cshtml
new file mode 100644
index 0000000..b43f042
--- /dev/null
+++ b/SS14.Admin/Pages/Bans/BanTemplates/Create.cshtml
@@ -0,0 +1,23 @@
+@page
+@model SS14.Admin.Pages.Bans.BanTemplates.Create
+
+
+@{
+ ViewData["Title"] = "Create new ban template";
+}
+
+
+
+
diff --git a/SS14.Admin/Pages/Bans/BanTemplates/Create.cshtml.cs b/SS14.Admin/Pages/Bans/BanTemplates/Create.cshtml.cs
new file mode 100644
index 0000000..bd9f40d
--- /dev/null
+++ b/SS14.Admin/Pages/Bans/BanTemplates/Create.cshtml.cs
@@ -0,0 +1,38 @@
+using Content.Server.Database;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace SS14.Admin.Pages.Bans.BanTemplates;
+
+[Authorize(Roles = "BAN")]
+[ValidateAntiForgeryToken]
+public class Create(PostgresServerDbContext dbContext) : PageModel
+{
+ [BindProperty] public string Title { get; set; } = "";
+
+ public void OnGet()
+ {
+
+ }
+
+ public async Task OnPostCreateAsync()
+ {
+ if (string.IsNullOrWhiteSpace(Title))
+ {
+ TempData["StatusMessage"] = "Error: name must not be empty";
+ return Page();
+ }
+
+ var template = new BanTemplate
+ {
+ Title = Title,
+ };
+
+ dbContext.BanTemplate.Add(template);
+
+ await dbContext.SaveChangesAsync();
+
+ return RedirectToPage("View", new { id = template.Id });
+ }
+}
diff --git a/SS14.Admin/Pages/Bans/BanTemplates/View.cshtml b/SS14.Admin/Pages/Bans/BanTemplates/View.cshtml
new file mode 100644
index 0000000..f6624cf
--- /dev/null
+++ b/SS14.Admin/Pages/Bans/BanTemplates/View.cshtml
@@ -0,0 +1,87 @@
+@page
+@using SS14.Admin.Helpers
+@model SS14.Admin.Pages.Bans.BanTemplates.View
+
+@{
+ ViewData["Title"] = $"Ban template: {Model.Template.Title}";
+}
+
+
+
+
+
+
diff --git a/SS14.Admin/Pages/Bans/BanTemplates/View.cshtml.cs b/SS14.Admin/Pages/Bans/BanTemplates/View.cshtml.cs
new file mode 100644
index 0000000..822bfa3
--- /dev/null
+++ b/SS14.Admin/Pages/Bans/BanTemplates/View.cshtml.cs
@@ -0,0 +1,88 @@
+using System.ComponentModel.DataAnnotations;
+using Content.Server.Database;
+using Content.Shared.Database;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.EntityFrameworkCore;
+using SS14.Admin.Helpers;
+
+namespace SS14.Admin.Pages.Bans.BanTemplates;
+
+[Authorize(Roles = "BAN")]
+[ValidateAntiForgeryToken]
+public class View(PostgresServerDbContext dbContext) : PageModel
+{
+ public BanTemplate Template = default!;
+
+ [BindProperty] public InputModel Input { get; set; } = new();
+
+ public sealed class InputModel
+ {
+ [Required] public string Title { get; set; } = "";
+ public int LengthMinutes { get; set; }
+ public string? Reason { get; set; }
+
+ [Display(Name = "Delete ban when expired")]
+ public bool AutoDelete { get; set; }
+
+ [Display(Name = "Hidden from player")] public bool Hidden { get; set; }
+ public NoteSeverity Severity { get; set; }
+ }
+
+ public async Task OnGetAsync(int id)
+ {
+ var template = await dbContext.BanTemplate.SingleOrDefaultAsync(t => t.Id == id);
+ if (template == null)
+ return NotFound();
+
+ Input.Hidden = template.Hidden;
+ Input.Severity = template.Severity;
+ Input.Reason = template.Reason;
+ Input.Title = template.Title;
+ Input.LengthMinutes = (int)template.Length.TotalMinutes;
+ Input.AutoDelete = template.AutoDelete;
+
+ Template = template;
+ return Page();
+ }
+
+ public async Task OnPostEditAsync(int id)
+ {
+ if (!ModelState.IsValid)
+ return RedirectToPage(new { id });
+
+ var template = await dbContext.BanTemplate.SingleOrDefaultAsync(t => t.Id == id);
+ if (template == null)
+ return NotFound();
+
+ if (string.IsNullOrWhiteSpace(Input.Title))
+ {
+ TempData["StatusMessage"] = "Error: title is empty";
+ return RedirectToPage(new { id });
+ }
+
+ template.Title = Input.Title;
+ template.Length = TimeSpan.FromMinutes(Input.LengthMinutes);
+ template.Reason = Input.Reason ?? "";
+ template.AutoDelete = Input.AutoDelete;
+ template.Hidden = Input.Hidden;
+ template.Severity = Input.Severity;
+
+ var flags = ServerBanExemptFlags.None;
+ foreach (var (value, _) in BanExemptions.GetExemptions())
+ {
+ if (Request.Form.TryGetValue($"exemption_{value}", out var checkValue) && checkValue == "on")
+ {
+ flags |= value;
+ }
+ }
+
+ template.ExemptFlags = flags;
+
+ await dbContext.SaveChangesAsync();
+
+ return RedirectToPage(new { id });
+ }
+}
diff --git a/SS14.Admin/Pages/Bans/Create.cshtml b/SS14.Admin/Pages/Bans/Create.cshtml
index 50f7f78..109c18d 100644
--- a/SS14.Admin/Pages/Bans/Create.cshtml
+++ b/SS14.Admin/Pages/Bans/Create.cshtml
@@ -1,4 +1,6 @@
@page
+@using Microsoft.AspNetCore.Mvc.TagHelpers
+@using SS14.Admin.Helpers
@model SS14.Admin.Pages.Bans.Create
@{
@@ -7,97 +9,137 @@
-