summaryrefslogtreecommitdiff
path: root/Project-Unite
diff options
context:
space:
mode:
authorMichael <[email protected]>2017-05-07 11:31:31 -0400
committerMichael <[email protected]>2017-05-07 11:31:31 -0400
commitf8fd937d3aa902186ac4fc885ce536119a55b1ad (patch)
tree97e3e6ccdf14acefebf13fe74f094164579f4578 /Project-Unite
parent6777edffa113afd063db18c811e90a43e6043064 (diff)
downloadproject-unite-f8fd937d3aa902186ac4fc885ce536119a55b1ad.tar.gz
project-unite-f8fd937d3aa902186ac4fc885ce536119a55b1ad.tar.bz2
project-unite-f8fd937d3aa902186ac4fc885ce536119a55b1ad.zip
ACP redesign
Diffstat (limited to 'Project-Unite')
-rw-r--r--Project-Unite/Controllers/AdminController.cs693
-rw-r--r--Project-Unite/Views/Admin/Index.cshtml87
2 files changed, 80 insertions, 700 deletions
diff --git a/Project-Unite/Controllers/AdminController.cs b/Project-Unite/Controllers/AdminController.cs
index 0ccfd84..a0f75c3 100644
--- a/Project-Unite/Controllers/AdminController.cs
+++ b/Project-Unite/Controllers/AdminController.cs
@@ -20,703 +20,12 @@ namespace Project_Unite.Controllers
{
private ApplicationDbContext db = new ApplicationDbContext();
+ [Authorize]
public ActionResult Index()
{
- ViewBag.Admin = true;
- return View();
- }
-
-
- public ActionResult DeleteForum(string id)
- {
- var frm = db.ForumCategories.FirstOrDefault(x => x.Id == id);
- if (frm == null)
- return new HttpStatusCodeResult(404);
-
- //Purge ALL DATA RELATED TO THIS CATEGORY.
- DeleteCategoryRecursive(frm);
- db.SaveChanges();
-
- return RedirectToAction("Forums");
- }
-
-
- public void DeleteCategoryRecursive(ForumCategory start)
- {
- foreach (var c in start.Children.ToArray())
- {
- DeleteCategoryRecursive(c);
- }
-
- foreach (var topic in start.Topics.ToArray())
- {
- DeleteTopic(topic);
- }
- db.ForumCategories.Remove(start);
-
- }
-
- public ActionResult CreateUser()
- {
- return View(new CreateUserModel());
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult CreateUser(CreateUserModel model)
- {
- var db = new ApplicationDbContext();
-
- var user = new ApplicationUser();
- user.AccessFailedCount = 0;
- user.BannedAt = DateTime.Now;
- user.Bio = "";
- user.Codepoints = 0;
- user.DisplayName = model.Username;
- user.Email = model.Email;
- user.EmailConfirmed = true;
- user.FullName = "";
- user.Hobbies = "";
- user.Id = Guid.NewGuid().ToString();
- user.IsBanned = false;
- user.IsMuted = false;
- user.IsPatreon = false;
- user.JoinedAt = DateTime.Now;
- user.LastKnownIPAddress = "127.0.0.1";
- user.LastLogin = DateTime.Now;
- user.LastMonthPaid = 0;
- user.LockoutEnabled = false;
- user.MajorVersion = 1;
- user.MinorVersion = 0;
- user.MutedAt = DateTime.Now;
- user.PasswordHash = "ResetYourPassword.";
- user.PhoneNumberConfirmed = false;
- user.Revision = 0;
- user.SecurityStamp = Guid.NewGuid().ToString();
- user.ShiftnetSubscription = 0;
- user.StoryPosition = 0;
- user.TwoFactorEnabled = false;
- user.UserName = model.Email;
-
- db.Users.Add(user);
-
- db.SaveChanges();
- var uman = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
- uman.AddToRole(user.Id, ACL.LowestPriorityRole().Name);
- uman.SendEmail(user.Id, "Welcome to the Project: Unite open alpha.", $@"**Hey there, {user.DisplayName}!
-
-Michael here - it's finally happening. Project: Unite is coming together and it's time we merge everyone's user accounts.
-
-Unlike previous ShiftOS site revamps, your account got migrated over. However, there's one thing you need to do. You need to [reset your password]({Url.Action("ForgotPassword", "Account")})! Just click that link and follow the instructions and we'll send you another email with details on how to get into your account.");
- return RedirectToAction("Users");
- }
-
- public ActionResult BackupAssets()
- {
- var db = new ApplicationDbContext();
- string backupDir = "~/Backups/Database";
- string backupServerDir = Server.MapPath(backupDir);
- if (!System.IO.Directory.Exists(backupServerDir))
- System.IO.Directory.CreateDirectory(backupServerDir);
-
- string backupUrl = backupDir.Remove(0, 1) + "/ShiftOS-" + DateTime.Now.ToString().Replace("/", "-").Replace(":", "-") + ".zip";
- string backupname = Path.Combine(backupServerDir, "ShiftOS-" + DateTime.Now.ToString().Replace("/", "-").Replace(":", "-") + ".zip");
-
- try
- {
- System.IO.Compression.ZipFile.CreateFromDirectory(Server.MapPath("~/Uploads"), backupname);
- }
- catch
- {
- return Content(backupname + "<br/><br/>" + Server.MapPath("~/Uploads"));
- }
- var backupData = new AssetBackup();
- backupData.Id = Guid.NewGuid().ToString();
- backupData.UserId = User.Identity.GetUserId();
- backupData.DownloadUrl = backupUrl;
- backupData.Timestamp = DateTime.Now;
- db.AssetBackups.Add(backupData);
- db.SaveChanges();
- return RedirectToAction("Backups");
- }
-
- public ActionResult BackupDatabase()
- {
- var db = new ApplicationDbContext();
- string backupDir = "~/Backups/Assets";
- string backupServerDir = Server.MapPath(backupDir);
- if (!System.IO.Directory.Exists(backupServerDir))
- System.IO.Directory.CreateDirectory(backupServerDir);
-
- string backupUrl = backupDir.Remove(0, 1) + "/ShiftOS-" + DateTime.Now.ToString() + ".sql";
- string backupname = backupServerDir + "\\ShiftOS-" + DateTime.Now.ToString() + ".sql";
- const string sqlCommand = @"BACKUP DATABASE [{0}] TO DISK = N'{1}' WITH NOFORMAT, NOINIT, NAME = N'ShiftOS Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10";
- int path = db.Database.ExecuteSqlCommand(System.Data.Entity.TransactionalBehavior.DoNotEnsureTransaction, string.Format(sqlCommand, db.Database.Connection.Database, backupname));
- var backupData = new DatabaseBackup();
- backupData.Id = Guid.NewGuid().ToString();
- backupData.UserId = User.Identity.GetUserId();
- backupData.DownloadUrl = backupUrl;
- backupData.Timestamp = DateTime.Now;
- db.Backups.Add(backupData);
- db.SaveChanges();
- return RedirectToAction("Backups");
- }
-
-
-
-
- public ActionResult Backups()
- {
- ViewBag.Admin = true;
- var backups = new BackupViewModel();
- var db = new ApplicationDbContext();
- backups.Databases = db.Backups.OrderByDescending(x => x.Timestamp);
- backups.AssetFolders = db.AssetBackups.OrderByDescending(x => x.Timestamp);
-
- return View(backups);
- }
-
- public void DeleteTopic(ForumTopic topic)
- {
- foreach(var post in topic.Posts.ToArray())
- {
- DeletePost(post);
- }
- string id = topic.Id;
- db.Likes.RemoveRange(db.Likes.Where(x => x.Topic == id));
- db.ForumTopics.Remove(db.ForumTopics.FirstOrDefault(x=>x.Id==id));
- }
-
- public ActionResult AccessControl()
- {
- var model = new Dictionary<string, ForumPermission[]>();
- var db = new ApplicationDbContext();
- var forums = db.ForumCategories.ToArray();
- foreach(var forum in forums)
- {
- ACL.UpdateACLDefinitions(forum.Id);
- if(forum.Id != "root")
- {
- if (!model.ContainsKey(forum.Id))
- {
- model.Add(forum.Id, forum.Permissions);
- }
- }
- }
- return View(new AdminAccessControlViewModel(model));
- }
-
- public ActionResult SetPermission(string id, string role, string permission)
- {
if (!ACL.Granted(User.Identity.Name, "CanAccessAdminCP"))
return new HttpStatusCodeResult(403);
- if (!ACL.Granted(User.Identity.Name, "CanEditRoles"))
- return new HttpStatusCodeResult(403);
- if (!ACL.Granted(User.Identity.Name, "CanEditForumCategories"))
- return new HttpStatusCodeResult(403);
-
- var db = new ApplicationDbContext();
- var frm = db.ForumCategories.FirstOrDefault(x => x.Id == id);
- if (frm == null)
- return new HttpStatusCodeResult(403);
- var rolePerm = db.ForumPermissions.Where(x => x.CategoryId == frm.Id).FirstOrDefault(x => x.RoleId == role);
- if (rolePerm == null)
- return new HttpStatusCodeResult(404);
-
- rolePerm.Permissions = (PermissionPreset)Enum.Parse(typeof(PermissionPreset), permission);
-
- db.AuditLogs.Add(new Models.AuditLog(User.Identity.GetUserId(), AuditLogLevel.Admin, "User altered the ACL definition for forum ID " + id + ", role ID " + role + ", to permission \"" + permission + "\"."));
- db.SaveChanges();
-
- return RedirectToAction("AccessControl");
-
- }
-
- public void DeletePost(ForumPost post)
- {
- string id = post.Id;
-
- db.ForumPosts.Remove(db.ForumPosts.FirstOrDefault(x=>x.Id==id));
- }
-
-
- // GET: Admin/Forums
- public ActionResult Forums()
- {
- ViewBag.Admin = true;
- var cats = db.ForumCategories.First(x => x.Id == "root").Children.Where(x=>x.Id != "root");
- return View(cats);
- }
-
-
-
- public ActionResult AddForumCategory(string parentId)
- {
- ViewBag.Admin = true;
- if (parentId == null)
- parentId = "root";
-
- var model = new AddForumCategoryViewModel();
- model.PossibleParents = GetForumCategories();
- model.Parent = (parentId);
- return View(model);
- }
-
- public ActionResult RaisePriority(string id)
- {
- var uid = User.Identity.Name;
- if(ACL.Granted(uid, "CanEditRoles"))
- {
- var db = new ApplicationDbContext();
- var role = db.Roles.FirstOrDefault(x => x.Id == id) as Role;
- if (role == null)
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
-
- if (role.Priority == db.Roles.Count() - 1)
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
-
- Role higherUp = null;
- foreach(var r in db.Roles)
- {
- if ((r as Role).Priority == role.Priority + 1)
- higherUp = r as Role;
- }
-
- if(higherUp != null)
- higherUp.Priority--;
- role.Priority++;
- db.SaveChanges();
-
- return RedirectToAction("Roles");
- }
- else
- {
- return new HttpStatusCodeResult(403);
- }
- }
-
- public async Task<ActionResult> RemoveUserFromRole(string id, string usr)
- {
- if(ACL.CanManageRole(User.Identity.Name, id))
- {
- var uman = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
- await uman.RemoveFromRoleAsync(usr, id);
- return RedirectToAction("RoleDetails", new { @id = id });
- }
- else
- {
- return new HttpStatusCodeResult(403);
- }
- }
-
- public ActionResult LowerPriority(string id)
- {
- var uid = User.Identity.Name;
- if (ACL.Granted(uid, "CanEditRoles"))
- {
- var db = new ApplicationDbContext();
- var role = db.Roles.FirstOrDefault(x => x.Id == id) as Role;
- if (role == null)
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
-
- if (role.Priority == 0)
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
-
- Role higherUp = null;
- foreach (var r in db.Roles)
- {
- if ((r as Role).Priority == role.Priority - 1)
- higherUp = r as Role;
- }
-
- if(higherUp != null)
- higherUp.Priority++;
- role.Priority--;
- db.SaveChanges();
-
- return RedirectToAction("Roles");
- }
- else
- {
- return new HttpStatusCodeResult(403);
- }
- }
-
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult AddForumCategory(AddForumCategoryViewModel model)
- {
- try
- {
- if (model == null)
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
-
- if (string.IsNullOrWhiteSpace(model.Name))
- {
- ViewBag.Error = "Please specify a name for this forum category.";
- return View(model);
- }
-
- string DisallowedChars = "/.,\\][;':\"|?><!@#$%^&*()_+-=`~}{ ";
-
- string id = model.Name.ToLower();
-
- foreach (var c in DisallowedChars)
- {
- id = id.Replace(c, '_');
- }
-
-
- var frm = new ForumCategory();
-
- frm.Name = model.Name;
- frm.Id = id;
-
- frm.Description = model.Description;
- frm.LinkUrl = null;
- frm.Parent = db.ForumCategories.FirstOrDefault(x => x.Id == model.Parent).Id;
- db.ForumCategories.Add(frm);
-
- db.SaveChanges();
- if (model.StealPermissionsFrom != "root")
- {
- var frmToSteal = db.ForumCategories.FirstOrDefault(x => x.Id == model.StealPermissionsFrom);
- ACL.UpdateACLDefinitions(frmToSteal.Id); //Just to be sure..
- foreach(var perm in frmToSteal.Permissions)
- {
- var aclEntry = new ForumPermission();
- aclEntry.CategoryId = frm.Id;
- aclEntry.RoleId = perm.RoleId;
- aclEntry.Permissions = perm.Permissions;
- aclEntry.Id = Guid.NewGuid().ToString();
- db.ForumPermissions.Add(aclEntry);
- }
- db.SaveChanges();
- }
- else
- {
- ACL.UpdateACLDefinitions(frm.Id);
- //This sets the permission data to the default values.
- }
-
-
-
- return RedirectToAction("Forums");
- }
- catch (Exception ex)
- {
- ViewBag.Error = ex.ToString();
- return View(model);
- }
- }
-
- public ActionResult AnonymizeUser(string id)
- {
- if (!ACL.Granted(User.Identity.Name, "CanAcessAdminCP"))
- return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
- if (!ACL.Granted(User.Identity.Name, "CanAnonymizeUser"))
- return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
- var db = new ApplicationDbContext();
- var user = db.Users.FirstOrDefault(x => x.Id == id);
- if (user == null)
- return new HttpStatusCodeResult(404);
-
- user.UserName = Guid.NewGuid().ToString() + "@system";
- user.Email = Guid.NewGuid().ToString() + "@system";
- user.PasswordHash = Guid.NewGuid().ToString();
- user.EmailConfirmed = false;
- user.Hobbies = "";
- user.Interests = "";
- user.Bio = @"# User anonymized.
-
-This user has been anonymized by an administrator.";
- user.AvatarUrl = "";
- user.BannerUrl = "";
- var uman = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
- foreach(var role in user.Roles)
- {
- uman.RemoveFromRole(user.Id, role.RoleId);
- }
- uman.AddToRole(user.Id, ACL.LowestPriorityRole().Id);
- db.SaveChanges();
- return RedirectToAction("Users");
- }
-
-
- public ActionResult Logs()
- {
- if (!ACL.Granted(User.Identity.Name, "CanAccessAdminCP"))
- return new HttpStatusCodeResult(403);
-
- var db = new ApplicationDbContext();
-
- return View(db.AuditLogs);
- }
-
-
- public ActionResult Users()
- {
- return View(new ApplicationDbContext().Users);
- }
-
- public ActionResult EditForum(string id)
- {
- ViewBag.Admin = true;
- var frm = db.ForumCategories.FirstOrDefault(x => x.Id == id);
- if (frm == null)
- return new HttpStatusCodeResult(404);
-
- var m = new AddForumCategoryViewModel();
- m.Id = frm.Id;
- m.Parent = frm.Parent;
- m.Description = frm.Description;
- m.Name = frm.Name;
- m.PossibleParents = GetForumCategories();
- return View(m);
- }
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult EditForum(string id, AddForumCategoryViewModel m)
- {
- try
- {
- m.Id = id;
- var frm = db.ForumCategories.FirstOrDefault(x => x.Id == id);
-
- frm.Name = m.Name;
- frm.Description = m.Description;
-
-
-
- frm.Parent = m.Parent;
- db.SaveChanges();
-
- return RedirectToAction("Forums");
- }
- catch (Exception ex)
- {
- ViewBag.Error = ex.ToString();
- return View(m);
- }
- }
-
-
-
-
-
-
- // GET: Admin
- public ActionResult Roles()
- {
- ViewBag.Admin = true;
- return View(db.IdentityRoles.ToList());
- }
-
- // GET: Admin/RoleDetails/5
- public ActionResult RoleDetails(string id)
- {
- ViewBag.Admin = true;
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- Role role = db.IdentityRoles.Find(id);
- if (role == null)
- {
- return HttpNotFound();
- }
- return View(role);
- }
-
- // GET: Admin/Create
- public ActionResult CreateRole()
- {
- ViewBag.Admin = true;
return View();
}
-
- // POST: Admin/Create
- // To protect from overposting attacks, please enable the specific properties you want to bind to, for
- // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult CreateRole(Role role)
- {
- if (ModelState.IsValid)
- {
- db.IdentityRoles.Add(role);
- db.SaveChanges();
- return RedirectToAction("Roles");
- }
-
- return View(role);
- }
-
- // GET: Admin/AddUserToRole
- public ActionResult AddUserToRole(string id = "")
- {
- ViewBag.Admin = true;
- var model = new AddUserToRoleViewModel();
-
- if (id == "")
- id = "user";
- model.RoleId = id;
- model.Users = new List<SelectListItem>();
- foreach (var u in db.Users.ToArray())
- {
- var user = u as ApplicationUser;
- model.Users.Add(new SelectListItem
- {
- Value = user.Id,
- Text = user.DisplayName
- });
- }
-
-
- model.Roles = new List<SelectListItem>();
- foreach(var role in db.Roles.ToArray())
- {
- var r = role as Role;
- model.Roles.Add(new SelectListItem
- {
- Value = r.Id,
- Text = r.Name
- });
- }
-
- return View(model);
- }
-
- // POST: Admin/AddUserToRole
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task<ActionResult> AddUserToRole(AddUserToRoleViewModel model)
- {
- var r = db.Roles.FirstOrDefault(role => role.Id == model.RoleId);
- var user = db.Users.FirstOrDefault(u => u.Id == model.Username);
- var uMan = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
-
- var isInRole = await uMan.IsInRoleAsync(user.Id, r.Name);
-
- if(isInRole)
- {
- ViewBag.Error = $"{model.Username} is already in the {r.Name} role.";
- return View(model);
- }
- else
- {
- await uMan.AddToRoleAsync(user.Id, r.Name);
- return RedirectToAction("Roles");
- }
-
-
- }
-
- // GET: Admin/Edit/5
- public ActionResult EditRole(string id)
- {
- ViewBag.Admin = true;
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- Role role = db.IdentityRoles.Find(id);
- if (role == null)
- {
- return HttpNotFound();
- }
- return View(role);
- }
-
- // POST: Admin/Edit/5
- // To protect from overposting attacks, please enable the specific properties you want to bind to, for
- // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult EditRole(Role role)
- {
- if (ModelState.IsValid)
- {
- db.Entry(role).State = EntityState.Modified;
- db.SaveChanges();
- return RedirectToAction("Index");
- }
- return View(role);
- }
-
- // GET: Admin/Delete/5
- public ActionResult DeleteRole(string id)
- {
- ViewBag.Admin = true;
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- Role role = db.IdentityRoles.Find(id);
- if (role == null)
- {
- return HttpNotFound();
- }
- return View(role);
- }
-
- // POST: Admin/Delete/5
- [HttpPost, ActionName("Delete")]
- [ValidateAntiForgeryToken]
- public ActionResult DeleteRoleConfirmed(string id)
- {
- var role = db.Roles.FirstOrDefault(x=>x.Id==id);
- db.Roles.Remove(role);
- db.SaveChanges();
- return RedirectToAction("Roles");
- }
-
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- db.Dispose();
- }
- base.Dispose(disposing);
- }
-
- public List<SelectListItem> GetForumCategories()
- {
- var items = new List<SelectListItem>();
- items.Add(new SelectListItem
- {
- Value = "root",
- Text = "Top Level"
- });
-
- foreach(var cat in getChildren("root", 1))
- {
- items.Add(cat);
- }
-
- return items;
-
- }
-
- private IEnumerable<SelectListItem> getChildren(string id, int dashcount)
- {
- var lst = new List<SelectListItem>();
- db = new ApplicationDbContext();
- foreach (var cat in db.ForumCategories.FirstOrDefault(c => c.Id == id).Children.Where(x=>x.Id != "root"))
- {
- string dashes = "";
- for (int i = 0; i <= dashcount; i++)
- dashes += "-";
- lst.Add(new SelectListItem
- {
- Text = dashes + " " + cat.Name,
- Value = cat.Id,
- });
- lst.AddRange(getChildren(cat.Id, dashcount + 1));
- }
- return lst;
- }
}
}
diff --git a/Project-Unite/Views/Admin/Index.cshtml b/Project-Unite/Views/Admin/Index.cshtml
index 815a20b..6547bd5 100644
--- a/Project-Unite/Views/Admin/Index.cshtml
+++ b/Project-Unite/Views/Admin/Index.cshtml
@@ -1,14 +1,85 @@
-
+@using Project_Unite.Models;
+@using Microsoft.AspNet.Identity;
@{
ViewBag.Title = "Administration Control Panel";
+ var db = new ApplicationDbContext();
}
-<p>Wow, an actual index page.</p>
+<div class="panel row">
+ <div class="panel-body">
+ <div class="col-xs-4">
+ <h4>Navigation</h4>
-<button class="btn btn-default" onclick="sendMessageToApi();">Send test notification.</button>
+ <ul class="nav nav-stacked" id="tabs" data-tabs="tabs" role="tablist">
+ <li class="active"><a data-toggle="tab" href="#a_index">Home</a></li>
+ <li><a data-toggle="tab" href="#a_site_config">Site Config</a></li>
+ <li><a data-toggle="tab" href="#a_users">Users</a></li>
+ <li><a data-toggle="tab" href="#a_roles">Roles</a></li>
+ <li><a data-toggle="tab" href="#a_forums">Forums</a></li>
+ <li><a data-toggle="tab" href="#a_skins">Skins</a></li>
+ <li><a data-toggle="tab" href="#a_groups">Groups</a></li>
+ <li><a data-toggle="tab" href="#a_backups">Backups</a></li>
+ <li><a data-toggle="tab" href="#a_logs">Audit Logs</a></li>
-<script>
- function sendMessageToApi() {
- $.ajax({ url: "/API/TestNotification" });
- }
-</script> \ No newline at end of file
+
+
+ </ul>
+ </div>
+ <div class="col-xs-8">
+ <div class="tab-content">
+ <div class="tab-pane fade in active" id="a_index">
+ <h2>Administration Control Panel</h2>
+
+ <p>Welcome to the ShiftOS Administration Control Panel. To begin administrating, select an option from the left.</p>
+
+ <h4>System status</h4>
+
+ <table class="table-condensed">
+ <tr>
+ <td><strong>Current user: </strong></td>
+ <td>@Html.UserLink(User.Identity.GetUserId())</td>
+ </tr>
+ <tr>
+ <td><strong>Users:</strong></td>
+ <td>@db.Users.Count()</td>
+ </tr>
+ <tr>
+ <td><strong>Forum Categories:</strong></td>
+ <td>@db.ForumCategories.Count()</td>
+ </tr>
+ <tr>
+ <td><strong>Posts:</strong></td>
+ <td>@db.ForumPosts.Count()</td>
+ </tr>
+ <tr>
+ <td><strong>Topics:</strong></td>
+ <td><strong>@db.ForumTopics.Count()</strong></td>
+ </tr>
+ <tr>
+ <td><strong>Skins:</strong></td>
+ <td>@db.Skins.Count()</td>
+ </tr>
+ <tr>
+ <td><strong>Releases:</strong></td>
+ <td>@db.Downloads.Count()</td>
+ </tr>
+ </table>
+
+ <hr/>
+
+ <h4>Shifting the website</h4>
+
+ <p>You can add new features to the website by modifying our GitHub repository. If you are a ShiftOS developer, chances are you have write access to the repository. If not, please contact Michael.</p>
+
+ <p>To clone the repository, use this command:</p>
+
+ @Html.Markdown(@"```bash
+git clone https://github.com/MichaelTheShifter/Project-Unite
+```")
+
+ <p>Your changes will be applied after you push your commits to this repository, or your pull request gets merged.</p>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file