Backend support for game saves.
This commit is contained in:
parent
90b9d3bb75
commit
4f07c62247
16 changed files with 231 additions and 18 deletions
96
LANCommander/Controllers/Api/GameSavesController.cs
Normal file
96
LANCommander/Controllers/Api/GameSavesController.cs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
using LANCommander.Data;
|
||||||
|
using LANCommander.Data.Models;
|
||||||
|
using LANCommander.Extensions;
|
||||||
|
using LANCommander.Models;
|
||||||
|
using LANCommander.SDK;
|
||||||
|
using LANCommander.Services;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace LANCommander.Controllers.Api
|
||||||
|
{
|
||||||
|
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class GameSavesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly GameSaveService GameSaveService;
|
||||||
|
private readonly GameService GameService;
|
||||||
|
private readonly UserManager<User> UserManager;
|
||||||
|
|
||||||
|
public GameSavesController(GameSaveService gameSaveService, GameService gameService, UserManager<User> userManager)
|
||||||
|
{
|
||||||
|
GameSaveService = gameSaveService;
|
||||||
|
GameService = gameService;
|
||||||
|
UserManager = userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<GameSave> Get(Guid id)
|
||||||
|
{
|
||||||
|
var gameSave = await GameSaveService.Get(id);
|
||||||
|
|
||||||
|
if (gameSave == null || gameSave.User == null)
|
||||||
|
throw new FileNotFoundException();
|
||||||
|
|
||||||
|
if (gameSave.User.UserName != HttpContext.User.Identity.Name)
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
|
||||||
|
return await GameSaveService.Get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/Download")]
|
||||||
|
public async Task<IActionResult> Download(Guid id)
|
||||||
|
{
|
||||||
|
var game = await GameService.Get(id);
|
||||||
|
|
||||||
|
if (game == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var user = await UserManager.GetUserAsync(User);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var path = GameSaveService.GetSavePath(game.Id, user.Id);
|
||||||
|
|
||||||
|
if (!System.IO.File.Exists(path))
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return File(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{game.Id}.zip");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/Upload")]
|
||||||
|
public async Task<IActionResult> Upload(Guid id, [FromForm] SaveUpload save)
|
||||||
|
{
|
||||||
|
// Arbitrary file size limit of 25MB
|
||||||
|
if (save.File.Length > (ByteSizeLib.ByteSize.BytesInMebiByte * 25))
|
||||||
|
return BadRequest("Save file archive is too large");
|
||||||
|
|
||||||
|
var game = await GameService.Get(id);
|
||||||
|
|
||||||
|
if (game == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var user = await UserManager.GetUserAsync(User);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var path = GameSaveService.GetSavePath(game.Id, user.Id);
|
||||||
|
|
||||||
|
var fileInfo = new FileInfo(path);
|
||||||
|
|
||||||
|
if (!Directory.Exists(fileInfo.Directory.FullName))
|
||||||
|
Directory.CreateDirectory(fileInfo.Directory.FullName);
|
||||||
|
|
||||||
|
using (var stream = System.IO.File.Create(path))
|
||||||
|
{
|
||||||
|
await save.File.CopyToAsync(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ namespace LANCommander.Controllers
|
||||||
model.TotalStorageSize = ByteSize.FromBytes(drives.Where(d => d.IsReady && d.Name == root).Sum(d => d.TotalSize));
|
model.TotalStorageSize = ByteSize.FromBytes(drives.Where(d => d.IsReady && d.Name == root).Sum(d => d.TotalSize));
|
||||||
model.TotalAvailableFreeSpace = ByteSize.FromBytes(drives.Where(d => d.IsReady && d.Name == root).Sum(d => d.AvailableFreeSpace));
|
model.TotalAvailableFreeSpace = ByteSize.FromBytes(drives.Where(d => d.IsReady && d.Name == root).Sum(d => d.AvailableFreeSpace));
|
||||||
model.TotalUploadDirectorySize = ByteSize.FromBytes(new DirectoryInfo("Upload").EnumerateFiles().Sum(f => f.Length));
|
model.TotalUploadDirectorySize = ByteSize.FromBytes(new DirectoryInfo("Upload").EnumerateFiles().Sum(f => f.Length));
|
||||||
|
model.TotalSaveDirectorySize = ByteSize.FromBytes(new DirectoryInfo("Save").EnumerateFiles().Sum(f => f.Length));
|
||||||
|
|
||||||
model.GameCount = GameService.Get().Count;
|
model.GameCount = GameService.Get().Count;
|
||||||
|
|
||||||
|
|
|
@ -45,11 +45,18 @@ namespace LANCommander.Controllers
|
||||||
|
|
||||||
foreach (var user in UserManager.Users)
|
foreach (var user in UserManager.Users)
|
||||||
{
|
{
|
||||||
|
var savePath = Path.Combine("Save", user.Id.ToString());
|
||||||
|
long saveSize = 0;
|
||||||
|
|
||||||
|
if (Directory.Exists(savePath))
|
||||||
|
saveSize = new DirectoryInfo(savePath).EnumerateFiles().Sum(f => f.Length);
|
||||||
|
|
||||||
users.Add(new UserViewModel()
|
users.Add(new UserViewModel()
|
||||||
{
|
{
|
||||||
Id = user.Id,
|
Id = user.Id,
|
||||||
UserName = user.UserName,
|
UserName = user.UserName,
|
||||||
Roles = await UserManager.GetRolesAsync(user)
|
Roles = await UserManager.GetRolesAsync(user),
|
||||||
|
SavesSize = saveSize
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,18 @@ namespace LANCommander.Data
|
||||||
g => g.HasOne<Company>().WithMany().HasForeignKey("PublisherId"),
|
g => g.HasOne<Company>().WithMany().HasForeignKey("PublisherId"),
|
||||||
g => g.HasOne<Game>().WithMany().HasForeignKey("GameId")
|
g => g.HasOne<Game>().WithMany().HasForeignKey("GameId")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
builder.Entity<User>()
|
||||||
|
.HasMany(u => u.GameSaves)
|
||||||
|
.WithOne(gs => gs.User)
|
||||||
|
.IsRequired(true)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.Entity<Game>()
|
||||||
|
.HasMany(g => g.GameSaves)
|
||||||
|
.WithOne(gs => gs.Game)
|
||||||
|
.IsRequired(true)
|
||||||
|
.OnDelete(DeleteBehavior.NoAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<Game>? Games { get; set; }
|
public DbSet<Game>? Games { get; set; }
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
Install,
|
Install,
|
||||||
Uninstall,
|
Uninstall,
|
||||||
NameChange,
|
NameChange,
|
||||||
KeyChange
|
KeyChange,
|
||||||
|
SaveUpload,
|
||||||
|
SaveDownload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ namespace LANCommander.Data.Models
|
||||||
public virtual ICollection<Company>? Developers { get; set; }
|
public virtual ICollection<Company>? Developers { get; set; }
|
||||||
public virtual ICollection<Archive>? Archives { get; set; }
|
public virtual ICollection<Archive>? Archives { get; set; }
|
||||||
public virtual ICollection<Script>? Scripts { get; set; }
|
public virtual ICollection<Script>? Scripts { get; set; }
|
||||||
|
public virtual ICollection<GameSave>? GameSaves { get; set; }
|
||||||
|
|
||||||
public string? ValidKeyRegex { get; set; }
|
public string? ValidKeyRegex { get; set; }
|
||||||
public virtual ICollection<Key>? Keys { get; set; }
|
public virtual ICollection<Key>? Keys { get; set; }
|
||||||
|
|
19
LANCommander/Data/Models/GameSave.cs
Normal file
19
LANCommander/Data/Models/GameSave.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace LANCommander.Data.Models
|
||||||
|
{
|
||||||
|
public class GameSave : BaseModel
|
||||||
|
{
|
||||||
|
public Guid GameId { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
[ForeignKey(nameof(GameId))]
|
||||||
|
[InverseProperty("GameSaves")]
|
||||||
|
public virtual Game? Game { get; set; }
|
||||||
|
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
[ForeignKey(nameof(UserId))]
|
||||||
|
[InverseProperty("GameSaves")]
|
||||||
|
public virtual User? User { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,5 +41,8 @@ namespace LANCommander.Data.Models
|
||||||
public string? RefreshToken { get; set; }
|
public string? RefreshToken { get; set; }
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public DateTime RefreshTokenExpiration { get; set; }
|
public DateTime RefreshTokenExpiration { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual ICollection<GameSave>? GameSaves { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("GamesId");
|
b.HasIndex("GamesId");
|
||||||
|
|
||||||
b.ToTable("CategoryGame");
|
b.ToTable("CategoryGame", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("GameDeveloper", b =>
|
modelBuilder.Entity("GameDeveloper", b =>
|
||||||
|
@ -44,7 +44,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("GameId");
|
b.HasIndex("GameId");
|
||||||
|
|
||||||
b.ToTable("GameDeveloper");
|
b.ToTable("GameDeveloper", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("GameGenre", b =>
|
modelBuilder.Entity("GameGenre", b =>
|
||||||
|
@ -59,7 +59,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("GenresId");
|
b.HasIndex("GenresId");
|
||||||
|
|
||||||
b.ToTable("GameGenre");
|
b.ToTable("GameGenre", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("GamePublisher", b =>
|
modelBuilder.Entity("GamePublisher", b =>
|
||||||
|
@ -74,7 +74,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("PublisherId");
|
b.HasIndex("PublisherId");
|
||||||
|
|
||||||
b.ToTable("GamePublisher");
|
b.ToTable("GamePublisher", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("GameTag", b =>
|
modelBuilder.Entity("GameTag", b =>
|
||||||
|
@ -89,7 +89,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("TagsId");
|
b.HasIndex("TagsId");
|
||||||
|
|
||||||
b.ToTable("GameTag");
|
b.ToTable("GameTag", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Action", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Action", b =>
|
||||||
|
@ -140,7 +140,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("UpdatedById");
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
b.ToTable("Actions");
|
b.ToTable("Actions", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Archive", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Archive", b =>
|
||||||
|
@ -194,7 +194,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("UpdatedById");
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
b.ToTable("Archive");
|
b.ToTable("Archive", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Category", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Category", b =>
|
||||||
|
@ -230,7 +230,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("UpdatedById");
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
b.ToTable("Categories");
|
b.ToTable("Categories", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
|
||||||
|
@ -261,7 +261,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("UpdatedById");
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
b.ToTable("Companies");
|
b.ToTable("Companies", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Game", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Game", b =>
|
||||||
|
@ -316,7 +316,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("UpdatedById");
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
b.ToTable("Games");
|
b.ToTable("Games", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Genre", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Genre", b =>
|
||||||
|
@ -347,7 +347,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("UpdatedById");
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
b.ToTable("Genres");
|
b.ToTable("Genres", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Key", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Key", b =>
|
||||||
|
@ -407,7 +407,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("UpdatedById");
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
b.ToTable("Keys");
|
b.ToTable("Keys", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.MultiplayerMode", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.MultiplayerMode", b =>
|
||||||
|
@ -458,7 +458,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("UpdatedById");
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
b.ToTable("MultiplayerModes");
|
b.ToTable("MultiplayerModes", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Role", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Role", b =>
|
||||||
|
@ -535,7 +535,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("UpdatedById");
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
b.ToTable("Scripts");
|
b.ToTable("Scripts", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Tag", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Tag", b =>
|
||||||
|
@ -566,7 +566,7 @@ namespace LANCommander.Migrations
|
||||||
|
|
||||||
b.HasIndex("UpdatedById");
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
b.ToTable("Tags");
|
b.ToTable("Tags", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.User", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.User", b =>
|
||||||
|
|
|
@ -7,6 +7,7 @@ namespace LANCommander.Models
|
||||||
public ByteSize TotalAvailableFreeSpace { get; set; }
|
public ByteSize TotalAvailableFreeSpace { get; set; }
|
||||||
public ByteSize TotalStorageSize { get; set; }
|
public ByteSize TotalStorageSize { get; set; }
|
||||||
public ByteSize TotalUploadDirectorySize { get; set; }
|
public ByteSize TotalUploadDirectorySize { get; set; }
|
||||||
|
public ByteSize TotalSaveDirectorySize { get; set; }
|
||||||
public ByteSize TotalOtherSize {
|
public ByteSize TotalOtherSize {
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
8
LANCommander/Models/SaveUpload.cs
Normal file
8
LANCommander/Models/SaveUpload.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace LANCommander.Models
|
||||||
|
{
|
||||||
|
public class SaveUpload
|
||||||
|
{
|
||||||
|
public Guid GameId { get; set; }
|
||||||
|
public IFormFile File { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,5 +5,6 @@
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public string UserName { get; set; }
|
public string UserName { get; set; }
|
||||||
public IEnumerable<string> Roles { get; set; }
|
public IEnumerable<string> Roles { get; set; }
|
||||||
|
public long SavesSize { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,4 +110,7 @@ if (!Directory.Exists("Upload"))
|
||||||
if (!Directory.Exists("Icon"))
|
if (!Directory.Exists("Icon"))
|
||||||
Directory.CreateDirectory("Icon");
|
Directory.CreateDirectory("Icon");
|
||||||
|
|
||||||
|
if (!Directory.Exists("Save"))
|
||||||
|
Directory.CreateDirectory("Save");
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
49
LANCommander/Services/GameSaveService.cs
Normal file
49
LANCommander/Services/GameSaveService.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using LANCommander.Data;
|
||||||
|
using LANCommander.Data.Models;
|
||||||
|
using LANCommander.Helpers;
|
||||||
|
|
||||||
|
namespace LANCommander.Services
|
||||||
|
{
|
||||||
|
public class GameSaveService : BaseDatabaseService<GameSave>
|
||||||
|
{
|
||||||
|
private readonly SettingService SettingService;
|
||||||
|
|
||||||
|
public GameSaveService(DatabaseContext dbContext, IHttpContextAccessor httpContextAccessor, SettingService settingService) : base(dbContext, httpContextAccessor)
|
||||||
|
{
|
||||||
|
SettingService = settingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Delete(GameSave entity)
|
||||||
|
{
|
||||||
|
FileHelpers.DeleteIfExists(GetSavePath(entity.Id));
|
||||||
|
|
||||||
|
return base.Delete(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSavePath(Guid gameId, Guid userId)
|
||||||
|
{
|
||||||
|
var save = Get(gs => gs.GameId == gameId && gs.UserId == userId).FirstOrDefault();
|
||||||
|
|
||||||
|
if (save == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return GetSavePath(save.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSavePath(Guid id)
|
||||||
|
{
|
||||||
|
// Use get with predicate to avoid async
|
||||||
|
var save = Get(gs => gs.Id == id).FirstOrDefault();
|
||||||
|
|
||||||
|
if (save == null)
|
||||||
|
return null;;
|
||||||
|
|
||||||
|
return GetSavePath(save);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSavePath(GameSave save)
|
||||||
|
{
|
||||||
|
return Path.Combine("Save", save.UserId.ToString(), $"{save.Id}.zip");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,15 +32,21 @@
|
||||||
<p class="mb-3">Storage Used: <strong>@(Model.TotalOtherSize + Model.TotalUploadDirectorySize) of @Model.TotalStorageSize</strong></p>
|
<p class="mb-3">Storage Used: <strong>@(Model.TotalOtherSize + Model.TotalUploadDirectorySize) of @Model.TotalStorageSize</strong></p>
|
||||||
<div class="progress progress-separated mb-3">
|
<div class="progress progress-separated mb-3">
|
||||||
<div class="progress-bar bg-primary" role="progressbar" style="width: @Math.Round((Model.TotalUploadDirectorySize.Bytes / Model.TotalStorageSize.Bytes) * 100)%;"></div>
|
<div class="progress-bar bg-primary" role="progressbar" style="width: @Math.Round((Model.TotalUploadDirectorySize.Bytes / Model.TotalStorageSize.Bytes) * 100)%;"></div>
|
||||||
|
<div class="progress-bar bg-dark" role="progressbar" style="width: @Math.Round((Model.TotalSaveDirectorySize.Bytes / Model.TotalStorageSize.Bytes) * 100)%;"></div>
|
||||||
<div class="progress-bar bg-info" role="progressbar" style="width: @Math.Round((Model.TotalOtherSize.Bytes / Model.TotalStorageSize.Bytes) * 100)%;"></div>
|
<div class="progress-bar bg-info" role="progressbar" style="width: @Math.Round((Model.TotalOtherSize.Bytes / Model.TotalStorageSize.Bytes) * 100)%;"></div>
|
||||||
<div class="progress-bar bg-success" role="progressbar" style="width: @Math.Round((Model.TotalAvailableFreeSpace.Bytes / Model.TotalStorageSize.Bytes) * 100)%;"></div>
|
<div class="progress-bar bg-success" role="progressbar" style="width: @Math.Round((Model.TotalAvailableFreeSpace.Bytes / Model.TotalStorageSize.Bytes) * 100)%;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto d-flex align-items-center pe-2">
|
<div class="col-auto d-flex align-items-center pe-2">
|
||||||
<span class="legend me-2 bg-primary"></span>
|
<span class="legend me-2 bg-primary"></span>
|
||||||
<span>Uploads</span>
|
<span>Games</span>
|
||||||
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-muted">@Model.TotalUploadDirectorySize</span>
|
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-muted">@Model.TotalUploadDirectorySize</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-auto d-flex align-items-center pe-2">
|
||||||
|
<span class="legend me-2 bg-dark"></span>
|
||||||
|
<span>Saves</span>
|
||||||
|
<span class="d-none d-md-inline d-lg-none d-xxl-inline ms-2 text-muted">@Model.TotalSaveDirectorySize</span>
|
||||||
|
</div>
|
||||||
<div class="col-auto d-flex align-items-center pe-2">
|
<div class="col-auto d-flex align-items-center pe-2">
|
||||||
<span class="legend me-2 bg-info"></span>
|
<span class="legend me-2 bg-info"></span>
|
||||||
<span>Other</span>
|
<span>Other</span>
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>Username</th>
|
<th>Username</th>
|
||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
|
<th>Saves</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -51,6 +52,9 @@
|
||||||
<td>
|
<td>
|
||||||
@String.Join(", ", item.Roles)
|
@String.Join(", ", item.Roles)
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
@ByteSizeLib.ByteSize.FromBytes(item.SavesSize)
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-list flex-nowrap justify-content-end">
|
<div class="btn-list flex-nowrap justify-content-end">
|
||||||
@if (!item.Roles.Any(r => r == "Administrator"))
|
@if (!item.Roles.Any(r => r == "Administrator"))
|
||||||
|
|
Loading…
Add table
Reference in a new issue