Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
Pat Hartl | 5ef16fc4cc | |
Pat Hartl | db8d3e4bf6 | |
Pat Hartl | f2462c0d20 | |
Pat Hartl | b87fe92c63 | |
Pat Hartl | c735556281 | |
Pat Hartl | d4bcde9d28 | |
Pat Hartl | 920b6b26f7 | |
Pat Hartl | d31fccc9f3 | |
Pat Hartl | c48d5b5d59 | |
Pat Hartl | dfb9f51acd | |
Pat Hartl | 03b0d9da93 |
|
@ -38,3 +38,13 @@ Packages:
|
||||||
- Full game download will be skipped if the game files already exist, see full release notes for more details
|
- Full game download will be skipped if the game files already exist, see full release notes for more details
|
||||||
- Play sessions are now recorded to the server with user ID, start time, and end time
|
- Play sessions are now recorded to the server with user ID, start time, and end time
|
||||||
- Connection status now updates correctly when authenticating through addon settings
|
- Connection status now updates correctly when authenticating through addon settings
|
||||||
|
- Version: 0.3.0
|
||||||
|
RequiredApiVersion: 6.0.0
|
||||||
|
ReleaseDate: 2023-12-03
|
||||||
|
PackageUrl: https://github.com/LANCommander/LANCommander/releases/download/v0.3.0/LANCommander.PlaynitePlugin_48e1bac7-e0a0-45d7-ba83-36f5e9e959fc_0_3_0.pext
|
||||||
|
Changelog:
|
||||||
|
- Save paths now support regex patterns (experimental)
|
||||||
|
- Fixed redistributable archive downloading
|
||||||
|
- Fixed redistributable scripts not being able to run as admin if marked as such
|
||||||
|
- Fixed game save downloading
|
||||||
|
- Fixed addon loading of YamlDotNet library
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<Select Mode="tags" TItem="Guid" TItemValue="Guid" @bind-Values="@SelectedValues" OnSelectedItemsChanged="OnSelectedItemsChanged" EnableSearch>
|
<Select Mode="tags" TItem="Guid" TItemValue="Guid" @bind-Values="@SelectedValues" OnSelectedItemsChanged="OnSelectedItemsChanged" EnableSearch>
|
||||||
<SelectOptions>
|
<SelectOptions>
|
||||||
@foreach (var entity in Entities)
|
@foreach (var entity in Entities.OrderBy(OptionLabelSelector))
|
||||||
{
|
{
|
||||||
<SelectOption TItemValue="Guid" TItem="Guid" Value="@entity.Id" Label="@OptionLabelSelector.Invoke(entity)" />
|
<SelectOption TItemValue="Guid" TItem="Guid" Value="@entity.Id" Label="@OptionLabelSelector.Invoke(entity)" />
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace LANCommander.Data
|
||||||
builder.ConfigureBaseRelationships<Data.Models.Action>();
|
builder.ConfigureBaseRelationships<Data.Models.Action>();
|
||||||
builder.ConfigureBaseRelationships<Archive>();
|
builder.ConfigureBaseRelationships<Archive>();
|
||||||
builder.ConfigureBaseRelationships<Category>();
|
builder.ConfigureBaseRelationships<Category>();
|
||||||
|
builder.ConfigureBaseRelationships<Collection>();
|
||||||
builder.ConfigureBaseRelationships<Company>();
|
builder.ConfigureBaseRelationships<Company>();
|
||||||
builder.ConfigureBaseRelationships<Game>();
|
builder.ConfigureBaseRelationships<Game>();
|
||||||
builder.ConfigureBaseRelationships<GameSave>();
|
builder.ConfigureBaseRelationships<GameSave>();
|
||||||
|
@ -180,6 +181,28 @@ namespace LANCommander.Data
|
||||||
.IsRequired(false)
|
.IsRequired(false)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Collection Relationships
|
||||||
|
builder.Entity<Collection>()
|
||||||
|
.HasMany(c => c.Games)
|
||||||
|
.WithMany(g => g.Collections)
|
||||||
|
.UsingEntity<Dictionary<string, object>>(
|
||||||
|
"CollectionGame",
|
||||||
|
cg => cg.HasOne<Game>().WithMany().HasForeignKey("GameId"),
|
||||||
|
cg => cg.HasOne<Collection>().WithMany().HasForeignKey("CollectionId")
|
||||||
|
);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Role Relationships
|
||||||
|
builder.Entity<Role>()
|
||||||
|
.HasMany(r => r.Collections)
|
||||||
|
.WithMany(c => c.Roles)
|
||||||
|
.UsingEntity<Dictionary<string, object>>(
|
||||||
|
"RoleCollection",
|
||||||
|
rc => rc.HasOne<Collection>().WithMany().HasForeignKey("CollectionId"),
|
||||||
|
rc => rc.HasOne<Role>().WithMany().HasForeignKey("RoleId")
|
||||||
|
);
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<Game>? Games { get; set; }
|
public DbSet<Game>? Games { get; set; }
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace LANCommander.Data.Models
|
||||||
|
{
|
||||||
|
[Table("Collections")]
|
||||||
|
public class Collection : BaseModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual ICollection<Game> Games { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual ICollection<Role> Roles { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,5 +39,6 @@ namespace LANCommander.Data.Models
|
||||||
|
|
||||||
public string? ValidKeyRegex { get; set; }
|
public string? ValidKeyRegex { get; set; }
|
||||||
public virtual ICollection<Key>? Keys { get; set; }
|
public virtual ICollection<Key>? Keys { get; set; }
|
||||||
|
public virtual ICollection<Collection> Collections { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,6 @@ namespace LANCommander.Data.Models
|
||||||
[Table("Roles")]
|
[Table("Roles")]
|
||||||
public class Role : IdentityRole<Guid>
|
public class Role : IdentityRole<Guid>
|
||||||
{
|
{
|
||||||
|
public virtual ICollection<Collection> Collections { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1807
LANCommander/Migrations/20231203202813_FixScriptDisplayBounds.Designer.cs
generated
Normal file
1807
LANCommander/Migrations/20231203202813_FixScriptDisplayBounds.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LANCommander.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class FixScriptDisplayBounds : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$Display.Width', '$Display.Bounds.Width')");
|
||||||
|
migrationBuilder.Sql("UPDATE Scripts SET Contents = REPLACE(Contents, '$Display.Height', '$Display.Bounds.Height')");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,124 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace LANCommander.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddCollections : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Collections",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
CreatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
CreatedById = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||||
|
UpdatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
UpdatedById = table.Column<Guid>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Collections", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Collections_AspNetUsers_CreatedById",
|
||||||
|
column: x => x.CreatedById,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Collections_AspNetUsers_UpdatedById",
|
||||||
|
column: x => x.UpdatedById,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CollectionGame",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
CollectionId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
GameId = table.Column<Guid>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CollectionGame", x => new { x.CollectionId, x.GameId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CollectionGame_Collections_CollectionId",
|
||||||
|
column: x => x.CollectionId,
|
||||||
|
principalTable: "Collections",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CollectionGame_Games_GameId",
|
||||||
|
column: x => x.GameId,
|
||||||
|
principalTable: "Games",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "RoleCollection",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
CollectionId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
RoleId = table.Column<Guid>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_RoleCollection", x => new { x.CollectionId, x.RoleId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_RoleCollection_AspNetRoles_RoleId",
|
||||||
|
column: x => x.RoleId,
|
||||||
|
principalTable: "AspNetRoles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_RoleCollection_Collections_CollectionId",
|
||||||
|
column: x => x.CollectionId,
|
||||||
|
principalTable: "Collections",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CollectionGame_GameId",
|
||||||
|
table: "CollectionGame",
|
||||||
|
column: "GameId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Collections_CreatedById",
|
||||||
|
table: "Collections",
|
||||||
|
column: "CreatedById");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Collections_UpdatedById",
|
||||||
|
table: "Collections",
|
||||||
|
column: "UpdatedById");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_RoleCollection_RoleId",
|
||||||
|
table: "RoleCollection",
|
||||||
|
column: "RoleId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CollectionGame");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "RoleCollection");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Collections");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,6 +36,21 @@ namespace LANCommander.Migrations
|
||||||
b.ToTable("CategoryGame");
|
b.ToTable("CategoryGame");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CollectionGame", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("CollectionId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("GameId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("CollectionId", "GameId");
|
||||||
|
|
||||||
|
b.HasIndex("GameId");
|
||||||
|
|
||||||
|
b.ToTable("CollectionGame");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("GameDeveloper", b =>
|
modelBuilder.Entity("GameDeveloper", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("DeveloperId")
|
b.Property<Guid>("DeveloperId")
|
||||||
|
@ -257,6 +272,37 @@ namespace LANCommander.Migrations
|
||||||
b.ToTable("Categories");
|
b.ToTable("Categories");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LANCommander.Data.Models.Collection", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CreatedById")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedOn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UpdatedById")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedOn")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CreatedById");
|
||||||
|
|
||||||
|
b.HasIndex("UpdatedById");
|
||||||
|
|
||||||
|
b.ToTable("Collections");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
|
@ -1157,6 +1203,21 @@ namespace LANCommander.Migrations
|
||||||
b.ToTable("AspNetUserTokens", (string)null);
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("RoleCollection", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("CollectionId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("RoleId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("CollectionId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("RoleCollection");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CategoryGame", b =>
|
modelBuilder.Entity("CategoryGame", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LANCommander.Data.Models.Category", null)
|
b.HasOne("LANCommander.Data.Models.Category", null)
|
||||||
|
@ -1172,6 +1233,21 @@ namespace LANCommander.Migrations
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CollectionGame", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LANCommander.Data.Models.Collection", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LANCommander.Data.Models.Game", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("GameId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("GameDeveloper", b =>
|
modelBuilder.Entity("GameDeveloper", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LANCommander.Data.Models.Company", null)
|
b.HasOne("LANCommander.Data.Models.Company", null)
|
||||||
|
@ -1332,6 +1408,23 @@ namespace LANCommander.Migrations
|
||||||
b.Navigation("UpdatedBy");
|
b.Navigation("UpdatedBy");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LANCommander.Data.Models.Collection", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CreatedById")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UpdatedById")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("CreatedBy");
|
||||||
|
|
||||||
|
b.Navigation("UpdatedBy");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Company", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
|
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
|
||||||
|
@ -1750,6 +1843,21 @@ namespace LANCommander.Migrations
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("RoleCollection", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("LANCommander.Data.Models.Collection", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("LANCommander.Data.Models.Role", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LANCommander.Data.Models.Category", b =>
|
modelBuilder.Entity("LANCommander.Data.Models.Category", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Children");
|
b.Navigation("Children");
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace LANCommander.Models
|
||||||
|
{
|
||||||
|
public class AddToCollectionOptions
|
||||||
|
{
|
||||||
|
public IEnumerable<Guid> GameIds { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace LANCommander.Models
|
||||||
|
{
|
||||||
|
public class RoleViewModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int Users { get; set; }
|
||||||
|
public int Collections { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
@page "/Collections/{id:guid}"
|
||||||
|
@page "/Collections/Add"
|
||||||
|
@using LANCommander.Data.Enums;
|
||||||
|
@using LANCommander.Extensions
|
||||||
|
@attribute [Authorize(Roles = "Administrator")]
|
||||||
|
@inject CollectionService CollectionService
|
||||||
|
@inject IMessageService MessageService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
<PageHeader>
|
||||||
|
<PageHeaderTitle>
|
||||||
|
<Input Size="@InputSize.Large" @bind-Value="@Collection.Name" />
|
||||||
|
</PageHeaderTitle>
|
||||||
|
<PageHeaderExtra>
|
||||||
|
<Button Type="@ButtonType.Primary" OnClick="Save">Save</Button>
|
||||||
|
</PageHeaderExtra>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<Table TItem="Game" DataSource="@Collection.Games" Responsive>
|
||||||
|
<Column TData="string" Title="Icon">
|
||||||
|
<Image Src="@GetIcon(context)" Height="32" Width="32" Preview="false"></Image>
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<PropertyColumn Property="g => g.Title" Sortable Filterable />
|
||||||
|
|
||||||
|
<PropertyColumn Property="g => g.ReleasedOn" Format="MM/dd/yyyy" Sortable Filterable />
|
||||||
|
|
||||||
|
<PropertyColumn Property="g => g.Singleplayer" Sortable Filterable>
|
||||||
|
<Checkbox Disabled="true" Checked="context.Singleplayer" />
|
||||||
|
</PropertyColumn>
|
||||||
|
|
||||||
|
<Column TData="bool" Title="Multiplayer">
|
||||||
|
<Checkbox Disabled="true" Checked="context.MultiplayerModes?.Count > 0" />
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column TData="string[]" Title="Developers">
|
||||||
|
@foreach (var dev in context.Developers)
|
||||||
|
{
|
||||||
|
<Tag>@dev.Name</Tag>
|
||||||
|
}
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column TData="string[]" Title="Publishers">
|
||||||
|
@foreach (var pub in context.Publishers)
|
||||||
|
{
|
||||||
|
<Tag>@pub.Name</Tag>
|
||||||
|
}
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column TData="string[]" Title="Genres">
|
||||||
|
@foreach (var genre in context.Genres)
|
||||||
|
{
|
||||||
|
<Tag>@genre.Name</Tag>
|
||||||
|
}
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<Column TData="Data.Enums.MultiplayerType[]" Title="Multiplayer Modes">
|
||||||
|
@foreach (var mode in context.MultiplayerModes.Select(mm => mm.Type).Distinct())
|
||||||
|
{
|
||||||
|
<Tag>@mode.GetDisplayName()</Tag>
|
||||||
|
}
|
||||||
|
</Column>
|
||||||
|
|
||||||
|
<ActionColumn Title="" Style="text-align: right">
|
||||||
|
<ChildContent>
|
||||||
|
<Space Direction="DirectionVHType.Horizontal">
|
||||||
|
<SpaceItem>
|
||||||
|
<Popconfirm OnConfirm="() => RemoveGame(context)" Title="Are you sure you want to remove this game from the collection?">
|
||||||
|
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
|
||||||
|
</Popconfirm>
|
||||||
|
</SpaceItem>
|
||||||
|
</Space>
|
||||||
|
</ChildContent>
|
||||||
|
</ActionColumn>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public Guid Id { get; set; }
|
||||||
|
|
||||||
|
Collection Collection;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (Id == Guid.Empty)
|
||||||
|
Collection = new Collection();
|
||||||
|
else
|
||||||
|
Collection = await CollectionService.Get(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Collection.Id != Guid.Empty)
|
||||||
|
{
|
||||||
|
Collection = await CollectionService.Update(Collection);
|
||||||
|
|
||||||
|
await MessageService.Success("Collection updated!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Collection = await CollectionService.Add(Collection);
|
||||||
|
|
||||||
|
NavigationManager.LocationChanged += NotifyCollectionAdded;
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo($"/Collections/{Collection.Id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await MessageService.Error("Could not save!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyCollectionAdded(object? sender, LocationChangedEventArgs e)
|
||||||
|
{
|
||||||
|
NavigationManager.LocationChanged -= NotifyCollectionAdded;
|
||||||
|
|
||||||
|
MessageService.Success("Collection added!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveGame(Game game)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Collection.Games.Remove(game);
|
||||||
|
|
||||||
|
await CollectionService.Update(Collection);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageService.Error("Game could not be removed from the collection!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetIcon(Game game)
|
||||||
|
{
|
||||||
|
var media = game?.Media?.FirstOrDefault(m => m.Type == Data.Enums.MediaType.Icon);
|
||||||
|
|
||||||
|
if (media != null)
|
||||||
|
return $"/api/Media/{media.Id}/Download?fileId={media.FileId}";
|
||||||
|
else
|
||||||
|
return "/favicon.ico";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
@page "/Collections"
|
||||||
|
@using Microsoft.EntityFrameworkCore;
|
||||||
|
@attribute [Authorize(Roles = "Administrator")]
|
||||||
|
@inject CollectionService CollectionService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IMessageService MessageService
|
||||||
|
|
||||||
|
<PageHeader Title="Collections">
|
||||||
|
<PageHeaderExtra>
|
||||||
|
<Space Direction="DirectionVHType.Horizontal">
|
||||||
|
<SpaceItem>
|
||||||
|
<Search Placeholder="Search" @bind-Value="Search" BindOnInput DebounceMilliseconds="150" OnChange="() => LoadData()" />
|
||||||
|
</SpaceItem>
|
||||||
|
</Space>
|
||||||
|
</PageHeaderExtra>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<TableColumnPicker @ref="Picker" Key="Collections" @bind-Visible="ColumnPickerVisible" />
|
||||||
|
|
||||||
|
<Table TItem="Collection" DataSource="@Collections" Loading="@Loading" PageSize="25" Responsive>
|
||||||
|
<PropertyColumn Property="r => r.Name" Sortable Hidden="@(Picker.IsColumnHidden("Name"))" />
|
||||||
|
<PropertyColumn Property="s => s.CreatedOn" Format="MM/dd/yyyy hh:mm tt" Sortable Hidden="@(Picker.IsColumnHidden("Created On"))" />
|
||||||
|
<PropertyColumn Property="s => s.CreatedBy" Sortable Hidden="@(Picker.IsColumnHidden("Created By"))">
|
||||||
|
@context.CreatedBy?.UserName
|
||||||
|
</PropertyColumn>
|
||||||
|
<PropertyColumn Property="g => g.UpdatedOn" Format="MM/dd/yyyy hh:mm tt" Sortable Hidden="@(Picker.IsColumnHidden("Updated On"))" />
|
||||||
|
<PropertyColumn Property="g => g.UpdatedBy" Sortable Hidden="@(Picker.IsColumnHidden("Updated By"))">
|
||||||
|
@context.UpdatedBy?.UserName
|
||||||
|
</PropertyColumn>
|
||||||
|
<Column Title="Games" TData="int">
|
||||||
|
@context.Games.Count
|
||||||
|
</Column>
|
||||||
|
<ActionColumn Title="" Style="text-align: right; white-space: nowrap">
|
||||||
|
<TitleTemplate>
|
||||||
|
<div style="text-align: right">
|
||||||
|
<Button Icon="@IconType.Outline.Edit" Type="@ButtonType.Text" OnClick="() => OpenColumnPicker()" />
|
||||||
|
</div>
|
||||||
|
</TitleTemplate>
|
||||||
|
<ChildContent>
|
||||||
|
<Space Direction="DirectionVHType.Horizontal">
|
||||||
|
<SpaceItem>
|
||||||
|
<a href="/Collections/@(context.Id)" class="ant-btn ant-btn-primary">Edit</a>
|
||||||
|
</SpaceItem>
|
||||||
|
<SpaceItem>
|
||||||
|
<Popconfirm OnConfirm="() => Delete(context)" Title="Are you sure you want to delete this Collection?">
|
||||||
|
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
|
||||||
|
</Popconfirm>
|
||||||
|
</SpaceItem>
|
||||||
|
</Space>
|
||||||
|
</ChildContent>
|
||||||
|
</ActionColumn>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
IEnumerable<Collection> Collections { get; set; } = new List<Collection>();
|
||||||
|
|
||||||
|
bool Loading = true;
|
||||||
|
|
||||||
|
string Search = "";
|
||||||
|
|
||||||
|
TableColumnPicker Picker;
|
||||||
|
bool ColumnPickerVisible = false;
|
||||||
|
|
||||||
|
protected override void OnAfterRender(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
LoadData();
|
||||||
|
|
||||||
|
Loading = false;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadData()
|
||||||
|
{
|
||||||
|
var fuzzySearch = Search.ToLower().Trim();
|
||||||
|
|
||||||
|
Collections = await CollectionService.Get(r => r.Name.ToLower().Contains(fuzzySearch)).OrderBy(r => r.Name).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Add()
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo("/Collections/Add");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Delete(Collection Collection)
|
||||||
|
{
|
||||||
|
Collections = new List<Collection>();
|
||||||
|
|
||||||
|
Loading = true;
|
||||||
|
|
||||||
|
await CollectionService.Delete(Collection);
|
||||||
|
|
||||||
|
Collections = await CollectionService.Get(x => true).OrderBy(r => r.Name).ToListAsync();
|
||||||
|
|
||||||
|
Loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenColumnPicker()
|
||||||
|
{
|
||||||
|
ColumnPickerVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CloseColumnPicker()
|
||||||
|
{
|
||||||
|
ColumnPickerVisible = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,94 +0,0 @@
|
||||||
@using System.Diagnostics;
|
|
||||||
@using LANCommander.Extensions;
|
|
||||||
@using AntDesign.Charts;
|
|
||||||
@using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
<Spin Spinning="Loading">
|
|
||||||
<Area @ref="Chart" Config="Config" />
|
|
||||||
</Spin>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public int TimerHistory { get; set; }
|
|
||||||
[Parameter] public int TimerInterval { get; set; }
|
|
||||||
|
|
||||||
IChartComponent? Chart;
|
|
||||||
System.Timers.Timer Timer;
|
|
||||||
bool Loading = true;
|
|
||||||
|
|
||||||
Dictionary<string, double[]> Data = new Dictionary<string, double[]>();
|
|
||||||
|
|
||||||
ConcurrentDictionary<string, PerformanceCounter> PerformanceCounters = new ConcurrentDictionary<string, PerformanceCounter>();
|
|
||||||
|
|
||||||
string JsConfig = @"{
|
|
||||||
meta: {
|
|
||||||
value: {
|
|
||||||
alias: 'Speed',
|
|
||||||
formatter: (v) => humanFileSize(v, true) + '/s'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}";
|
|
||||||
|
|
||||||
AreaConfig Config = new AreaConfig
|
|
||||||
{
|
|
||||||
Name = "Network Download Rate",
|
|
||||||
Padding = "auto",
|
|
||||||
SeriesField = "series",
|
|
||||||
YField = "value",
|
|
||||||
XField = "index",
|
|
||||||
Animation = false,
|
|
||||||
XAxis = new ValueCatTimeAxis
|
|
||||||
{
|
|
||||||
Visible = false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
if (Timer == null)
|
|
||||||
{
|
|
||||||
Timer = new System.Timers.Timer();
|
|
||||||
|
|
||||||
Timer.Interval = TimerInterval;
|
|
||||||
|
|
||||||
Timer.Elapsed += async (s, e) =>
|
|
||||||
{
|
|
||||||
await RefreshData();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
await Chart.UpdateChart(Config, null, null, JsConfig);
|
|
||||||
|
|
||||||
Timer.Start();
|
|
||||||
|
|
||||||
Loading = false;
|
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshData()
|
|
||||||
{
|
|
||||||
#if WINDOWS
|
|
||||||
var category = new PerformanceCounterCategory("Network Interface");
|
|
||||||
|
|
||||||
foreach (var instance in category.GetInstanceNames())
|
|
||||||
{
|
|
||||||
if (!Data.ContainsKey(instance))
|
|
||||||
Data[instance] = new double[TimerHistory];
|
|
||||||
|
|
||||||
if (!PerformanceCounters.ContainsKey(instance))
|
|
||||||
PerformanceCounters[instance] = new PerformanceCounter("Network Interface", "Bytes Received/sec", instance);
|
|
||||||
|
|
||||||
Data[instance] = Data[instance].ShiftArrayAndInsert((double)PerformanceCounters[instance].NextValue(), TimerHistory);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Chart.ChangeData(Data.SelectMany(x => x.Value.Select((y, i) => new { value = y, index = i, series = x.Key })), true);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
@using System.Diagnostics;
|
|
||||||
@using LANCommander.Extensions;
|
|
||||||
@using AntDesign.Charts;
|
|
||||||
@using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
<Spin Spinning="Loading">
|
|
||||||
<Area @ref="Chart" Config="Config" />
|
|
||||||
</Spin>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public int TimerHistory { get; set; }
|
|
||||||
[Parameter] public int TimerInterval { get; set; }
|
|
||||||
|
|
||||||
IChartComponent? Chart;
|
|
||||||
System.Timers.Timer Timer;
|
|
||||||
bool Loading = true;
|
|
||||||
|
|
||||||
Dictionary<string, double[]> Data = new Dictionary<string, double[]>();
|
|
||||||
|
|
||||||
ConcurrentDictionary<string, PerformanceCounter> PerformanceCounters = new ConcurrentDictionary<string, PerformanceCounter>();
|
|
||||||
|
|
||||||
string JsConfig = @"{
|
|
||||||
meta: {
|
|
||||||
value: {
|
|
||||||
alias: 'Speed',
|
|
||||||
formatter: (v) => humanFileSize(v, true) + '/s'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}";
|
|
||||||
|
|
||||||
AreaConfig Config = new AreaConfig
|
|
||||||
{
|
|
||||||
Name = "Network Upload Rate",
|
|
||||||
Padding = "auto",
|
|
||||||
SeriesField = "series",
|
|
||||||
YField = "value",
|
|
||||||
XField = "index",
|
|
||||||
Animation = false,
|
|
||||||
XAxis = new ValueCatTimeAxis
|
|
||||||
{
|
|
||||||
Visible = false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
if (Timer == null)
|
|
||||||
{
|
|
||||||
Timer = new System.Timers.Timer();
|
|
||||||
|
|
||||||
Timer.Interval = TimerInterval;
|
|
||||||
|
|
||||||
Timer.Elapsed += async (s, e) =>
|
|
||||||
{
|
|
||||||
await RefreshData();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
await Chart.UpdateChart(Config, null, null, JsConfig);
|
|
||||||
Timer.Start();
|
|
||||||
|
|
||||||
Loading = false;
|
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshData()
|
|
||||||
{
|
|
||||||
#if WINDOWS
|
|
||||||
var category = new PerformanceCounterCategory("Network Interface");
|
|
||||||
|
|
||||||
foreach (var instance in category.GetInstanceNames())
|
|
||||||
{
|
|
||||||
if (!Data.ContainsKey(instance))
|
|
||||||
Data[instance] = new double[TimerHistory];
|
|
||||||
|
|
||||||
if (!PerformanceCounters.ContainsKey(instance))
|
|
||||||
PerformanceCounters[instance] = new PerformanceCounter("Network Interface", "Bytes Sent/sec", instance);
|
|
||||||
|
|
||||||
Data[instance] = Data[instance].ShiftArrayAndInsert((double)PerformanceCounters[instance].NextValue(), TimerHistory);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Chart.ChangeData(Data.SelectMany(x => x.Value.Select((y, i) => new { value = y, index = i, series = x.Key })), true);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
@using AntDesign.Charts
|
||||||
|
@using ByteSizeLib
|
||||||
|
@inject PlaySessionService PlaySessionService
|
||||||
|
|
||||||
|
<Spin Spinning="Loading">
|
||||||
|
<Pie Data="Data" Config="Config" JsConfig="@JsConfig" />
|
||||||
|
</Spin>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
object[] Data;
|
||||||
|
|
||||||
|
bool Loading = true;
|
||||||
|
|
||||||
|
string JsConfig = @"{
|
||||||
|
meta: {
|
||||||
|
value: {
|
||||||
|
alias: 'Overall Playtime',
|
||||||
|
formatter: (v) => new Date(v * 1000).toISOString().slice(11, 19)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
visible: true,
|
||||||
|
type: 'outer-center'
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
PieConfig Config = new PieConfig
|
||||||
|
{
|
||||||
|
Radius = 0.8,
|
||||||
|
AngleField = "value",
|
||||||
|
ColorField = "type",
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
Dictionary<string, TimeSpan> playtimes = new Dictionary<string, TimeSpan>();
|
||||||
|
|
||||||
|
var sessions = await PlaySessionService.Get();
|
||||||
|
|
||||||
|
foreach (var gameSessions in sessions.Where(s => s.GameId.HasValue && s.GameId.Value != Guid.Empty).GroupBy(s => s.GameId))
|
||||||
|
{
|
||||||
|
var total = new TimeSpan();
|
||||||
|
|
||||||
|
foreach (var session in gameSessions.Where(gs => gs.Start != null && gs.End != null))
|
||||||
|
{
|
||||||
|
total = total.Add(session.End.Value.Subtract(session.Start.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
playtimes[gameSessions.First().Game.Title] = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
Data = playtimes.Select(pt => new
|
||||||
|
{
|
||||||
|
type = pt.Key,
|
||||||
|
value = (int)pt.Value.TotalSeconds
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
Loading = false;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,85 +0,0 @@
|
||||||
@using System.Diagnostics;
|
|
||||||
@using LANCommander.Extensions;
|
|
||||||
@using AntDesign.Charts;
|
|
||||||
|
|
||||||
<Spin Spinning="Loading">
|
|
||||||
<Area @ref="Chart" Config="Config" />
|
|
||||||
</Spin>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public int TimerHistory { get; set; }
|
|
||||||
[Parameter] public int TimerInterval { get; set; }
|
|
||||||
IChartComponent? Chart;
|
|
||||||
System.Timers.Timer Timer;
|
|
||||||
bool Loading = true;
|
|
||||||
|
|
||||||
double[] Data;
|
|
||||||
|
|
||||||
PerformanceCounter PerformanceCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
|
|
||||||
|
|
||||||
string JsConfig = @"{
|
|
||||||
meta: {
|
|
||||||
value: {
|
|
||||||
alias: '% Usage',
|
|
||||||
formatter: (v) => v + '%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}";
|
|
||||||
|
|
||||||
AreaConfig Config = new AreaConfig
|
|
||||||
{
|
|
||||||
Name = "Processor Utilization",
|
|
||||||
Padding = "auto",
|
|
||||||
YField = "value",
|
|
||||||
XField = "index",
|
|
||||||
Animation = false,
|
|
||||||
IsPercent = true,
|
|
||||||
YAxis = new ValueAxis
|
|
||||||
{
|
|
||||||
Min = 0,
|
|
||||||
Max = 100
|
|
||||||
},
|
|
||||||
XAxis = new ValueCatTimeAxis
|
|
||||||
{
|
|
||||||
Visible = false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
if (Timer == null)
|
|
||||||
{
|
|
||||||
Timer = new System.Timers.Timer();
|
|
||||||
|
|
||||||
Timer.Interval = TimerInterval;
|
|
||||||
|
|
||||||
Timer.Elapsed += async (s, e) =>
|
|
||||||
{
|
|
||||||
await RefreshData();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
await Chart.UpdateChart(Config, null, null, JsConfig);
|
|
||||||
Timer.Start();
|
|
||||||
|
|
||||||
Loading = false;
|
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RefreshData()
|
|
||||||
{
|
|
||||||
#if WINDOWS
|
|
||||||
Data = Data.ShiftArrayAndInsert((double)Math.Ceiling(PerformanceCounter.NextValue()), TimerHistory);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Chart.ChangeData(Data.Select((x, i) => new { value = x, index = i }), true);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,25 +7,9 @@
|
||||||
|
|
||||||
<GridRow Gutter="(16, 16)">
|
<GridRow Gutter="(16, 16)">
|
||||||
<GridCol Xs="24" Md="12">
|
<GridCol Xs="24" Md="12">
|
||||||
<Card Title="Network Download Rate">
|
<Card Title="Overall Playtime">
|
||||||
<Body>
|
<Body>
|
||||||
<NetworkDownloadRate TimerHistory="60" TimerInterval="1000" />
|
<OverallPlaytime />
|
||||||
</Body>
|
|
||||||
</Card>
|
|
||||||
</GridCol>
|
|
||||||
|
|
||||||
<GridCol Xs="24" Md="12">
|
|
||||||
<Card Title="Network Upload Rate">
|
|
||||||
<Body>
|
|
||||||
<NetworkUploadRate TimerHistory="60" TimerInterval="1000" />
|
|
||||||
</Body>
|
|
||||||
</Card>
|
|
||||||
</GridCol>
|
|
||||||
|
|
||||||
<GridCol Xs="24" Md="12">
|
|
||||||
<Card Title="CPU Usage (%)">
|
|
||||||
<Body>
|
|
||||||
<ProcessorUtilization TimerHistory="60" TimerInterval="1000" />
|
|
||||||
</Body>
|
</Body>
|
||||||
</Card>
|
</Card>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<Input Type="text" @bind-Value="context.Name" />
|
<Input Type="text" @bind-Value="context.Name" />
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="a => a.Path">
|
<PropertyColumn Property="a => a.Path">
|
||||||
<FilePicker @bind-Value="context.Path" ArchiveId="@ArchiveId" AllowDirectories="true" />
|
<FilePicker @bind-Value="context.Path" ArchiveId="@ArchiveId" AllowDirectories="true" Prefix="{InstallDir}\" Title="Select Action Executable" />
|
||||||
</PropertyColumn>
|
</PropertyColumn>
|
||||||
<PropertyColumn Property="a => a.Arguments">
|
<PropertyColumn Property="a => a.Arguments">
|
||||||
<Input Type="text" @bind-Value="context.Arguments" />
|
<Input Type="text" @bind-Value="context.Arguments" />
|
||||||
|
@ -90,43 +90,6 @@
|
||||||
Move(action.SortOrder, action.SortOrder + 1);
|
Move(action.SortOrder, action.SortOrder + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void BrowseForActionPath(Data.Models.Action action)
|
|
||||||
{
|
|
||||||
var modalOptions = new ModalOptions()
|
|
||||||
{
|
|
||||||
Title = "Choose Action Executable",
|
|
||||||
Maximizable = false,
|
|
||||||
DefaultMaximized = true,
|
|
||||||
Closable = true,
|
|
||||||
OkText = "Select File"
|
|
||||||
};
|
|
||||||
|
|
||||||
var browserOptions = new FilePickerOptions()
|
|
||||||
{
|
|
||||||
ArchiveId = ArchiveId,
|
|
||||||
Select = true,
|
|
||||||
Multiple = false
|
|
||||||
};
|
|
||||||
|
|
||||||
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
|
|
||||||
|
|
||||||
modalRef.OnOk = (results) =>
|
|
||||||
{
|
|
||||||
action.Path = results.FirstOrDefault().Path;
|
|
||||||
|
|
||||||
var parts = action.Path.Split('/');
|
|
||||||
|
|
||||||
if (parts.Length > 1)
|
|
||||||
{
|
|
||||||
action.Path = parts.Last();
|
|
||||||
action.WorkingDirectory = "{InstallDir}/" + String.Join('/', parts.Take(parts.Length - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
return Task.CompletedTask;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Move(int oldIndex, int newIndex)
|
private void Move(int oldIndex, int newIndex)
|
||||||
{
|
{
|
||||||
foreach (var action in Actions)
|
foreach (var action in Actions)
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
@using LANCommander.Models
|
||||||
|
@inherits FeedbackComponent<AddToCollectionOptions, Collection>
|
||||||
|
@inject CollectionService CollectionService
|
||||||
|
@inject GameService GameService
|
||||||
|
@inject IMessageService MessageService
|
||||||
|
|
||||||
|
<Select
|
||||||
|
TItem="Collection"
|
||||||
|
TItemValue="Guid"
|
||||||
|
DataSource="@Collections"
|
||||||
|
@bind-Value="SelectedCollection"
|
||||||
|
LabelName="@nameof(Collection.Name)"
|
||||||
|
ValueName="@nameof(Collection.Id)"
|
||||||
|
Placeholder="Select a Collection"
|
||||||
|
DropdownRender="@DropdownRender"
|
||||||
|
OnSelectedItemChanged="OnSelectedItemChanged" />
|
||||||
|
|
||||||
|
@code {
|
||||||
|
ICollection<Collection> Collections = new List<Collection>();
|
||||||
|
|
||||||
|
Guid SelectedCollection;
|
||||||
|
|
||||||
|
string NewCollectionName;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadData()
|
||||||
|
{
|
||||||
|
Collections = (await CollectionService.Get()).OrderBy(c => c.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RenderFragment DropdownRender(RenderFragment originNode)
|
||||||
|
{
|
||||||
|
RenderFragment customDropdownRender =
|
||||||
|
@<Template>
|
||||||
|
<div>
|
||||||
|
@originNode
|
||||||
|
<Divider Style="margin: 4px 0"></Divider>
|
||||||
|
<div style="display: flex; flex-wrap: nowrap; padding: 8px">
|
||||||
|
<Input Style="flex: auto" @bind-Value="@NewCollectionName" />
|
||||||
|
<a style="flex: none; padding: 8px; display: block; cursor: pointer" @onclick="AddCollection">
|
||||||
|
<Icon Type="plus" Theme="outline"></Icon>
|
||||||
|
Add New Collection
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Template>
|
||||||
|
;
|
||||||
|
|
||||||
|
return customDropdownRender;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddCollection(MouseEventArgs args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrWhiteSpace(NewCollectionName))
|
||||||
|
{
|
||||||
|
await CollectionService.Add(new Collection()
|
||||||
|
{
|
||||||
|
Name = NewCollectionName
|
||||||
|
});
|
||||||
|
|
||||||
|
await LoadData();
|
||||||
|
|
||||||
|
MessageService.Success("Collection added!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageService.Error("Could not add a new collection!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectedItemChanged(Collection collection)
|
||||||
|
{
|
||||||
|
SelectedCollection = collection.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
|
||||||
|
{
|
||||||
|
var collection = await CollectionService.Get(SelectedCollection);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var gameId in Options.GameIds.Where(gid => collection.Games != null && !collection.Games.Any(g => g.Id == gid)))
|
||||||
|
{
|
||||||
|
var game = await GameService.Get(gameId);
|
||||||
|
|
||||||
|
collection.Games.Add(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
await CollectionService.Update(collection);
|
||||||
|
|
||||||
|
MessageService.Success("Added to collection!");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageService.Error("Could not add to collection!");
|
||||||
|
}
|
||||||
|
|
||||||
|
await base.OkCancelRefWithResult!.OnOk(collection);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,18 +2,26 @@
|
||||||
@using AntDesign.TableModels;
|
@using AntDesign.TableModels;
|
||||||
@using LANCommander.Extensions;
|
@using LANCommander.Extensions;
|
||||||
@using System.ComponentModel.DataAnnotations;
|
@using System.ComponentModel.DataAnnotations;
|
||||||
|
@using LANCommander.Models
|
||||||
|
@using LANCommander.Pages.Games.Components
|
||||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
|
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
|
||||||
@using Microsoft.EntityFrameworkCore;
|
@using Microsoft.EntityFrameworkCore;
|
||||||
@using System.Web
|
@using System.Web
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@inject GameService GameService
|
@inject GameService GameService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ModalService ModalService
|
||||||
@inject IMessageService MessageService
|
@inject IMessageService MessageService
|
||||||
|
|
||||||
<PageHeader Title="Games" Subtitle="@Games.Count().ToString()">
|
<PageHeader Title="Games" Subtitle="@Games.Count().ToString()">
|
||||||
<PageHeaderExtra>
|
<PageHeaderExtra>
|
||||||
<Space Direction="DirectionVHType.Horizontal">
|
<Space Direction="DirectionVHType.Horizontal">
|
||||||
|
@if (Selected != null && Selected.Count() > 0)
|
||||||
|
{
|
||||||
|
<SpaceItem>
|
||||||
|
<Button OnClick="() => AddToCollection()" Type="@ButtonType.Primary">Add to Collection</Button>
|
||||||
|
</SpaceItem>
|
||||||
|
}
|
||||||
<SpaceItem>
|
<SpaceItem>
|
||||||
<Search Placeholder="Search" @bind-Value="Search" BindOnInput DebounceMilliseconds="250" OnChange="SearchChanged" />
|
<Search Placeholder="Search" @bind-Value="Search" BindOnInput DebounceMilliseconds="250" OnChange="SearchChanged" />
|
||||||
</SpaceItem>
|
</SpaceItem>
|
||||||
|
@ -26,7 +34,8 @@
|
||||||
|
|
||||||
<TableColumnPicker @ref="Picker" Key="Games" @bind-Visible="ColumnPickerVisible" />
|
<TableColumnPicker @ref="Picker" Key="Games" @bind-Visible="ColumnPickerVisible" />
|
||||||
|
|
||||||
<Table TItem="Game" DataSource="@Games" Loading="@Loading" PageSize="@PageSize" PageIndex="@PageIndex" OnPageIndexChange="PageIndexChanged" OnPageSizeChange="PageSizeChanged" Responsive>
|
<Table TItem="Game" DataSource="@Games" @bind-SelectedRows="Selected" Loading="@Loading" PageSize="@PageSize" PageIndex="@PageIndex" OnPageIndexChange="PageIndexChanged" OnPageSizeChange="PageSizeChanged" Responsive>
|
||||||
|
<Selection Key="@(context.Id.ToString())" />
|
||||||
<Column TData="string" Title="Icon" Hidden="@(Picker.IsColumnHidden("Icon"))">
|
<Column TData="string" Title="Icon" Hidden="@(Picker.IsColumnHidden("Icon"))">
|
||||||
<Image Src="@GetIcon(context)" Height="32" Width="32" Preview="false"></Image>
|
<Image Src="@GetIcon(context)" Height="32" Width="32" Preview="false"></Image>
|
||||||
</Column>
|
</Column>
|
||||||
|
@ -127,6 +136,8 @@
|
||||||
|
|
||||||
bool Visibility = false;
|
bool Visibility = false;
|
||||||
|
|
||||||
|
IEnumerable<Game> Selected;
|
||||||
|
|
||||||
TableColumnPicker Picker;
|
TableColumnPicker Picker;
|
||||||
bool ColumnPickerVisible = false;
|
bool ColumnPickerVisible = false;
|
||||||
|
|
||||||
|
@ -231,6 +242,31 @@
|
||||||
Loading = false;
|
Loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void AddToCollection()
|
||||||
|
{
|
||||||
|
var modalOptions = new ModalOptions()
|
||||||
|
{
|
||||||
|
Title = "Add to Collection",
|
||||||
|
Maximizable = false,
|
||||||
|
DefaultMaximized = false,
|
||||||
|
Closable = true,
|
||||||
|
OkText = "Add"
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = new AddToCollectionOptions()
|
||||||
|
{
|
||||||
|
GameIds = Selected.Select(g => g.Id)
|
||||||
|
};
|
||||||
|
|
||||||
|
var modalRef = await ModalService.CreateModalAsync<AddToCollectionDialog, AddToCollectionOptions, Collection>(modalOptions, options);
|
||||||
|
|
||||||
|
modalRef.OnOk = async (collection) =>
|
||||||
|
{
|
||||||
|
Selected = null;
|
||||||
|
await LoadData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OpenColumnPicker()
|
private async Task OpenColumnPicker()
|
||||||
{
|
{
|
||||||
ColumnPickerVisible = true;
|
ColumnPickerVisible = true;
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
@page "/Settings/Roles"
|
||||||
|
@using LANCommander.Models;
|
||||||
|
@layout SettingsLayout
|
||||||
|
@inject RoleManager<Role> RoleManager
|
||||||
|
@inject UserManager<User> UserManager
|
||||||
|
@inject IMessageService MessageService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@attribute [Authorize(Roles = "Administrator")]
|
||||||
|
|
||||||
|
<PageHeader Title="Roles" Subtitle="@Roles.Count().ToString()">
|
||||||
|
<PageHeaderExtra>
|
||||||
|
<Space Direction="DirectionVHType.Horizontal">
|
||||||
|
<SpaceItem>
|
||||||
|
<Button OnClick="() => ShowNewRoleDialog()" Type="@ButtonType.Primary">Add Role</Button>
|
||||||
|
</SpaceItem>
|
||||||
|
</Space>
|
||||||
|
</PageHeaderExtra>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<div style="padding: 0 24px;">
|
||||||
|
<Table TItem="RoleViewModel" DataSource="@Roles" Loading="@(Loading)" Responsive>
|
||||||
|
<PropertyColumn Property="r => r.Name" Title="Name" />
|
||||||
|
<PropertyColumn Property="r => r.Collections" Title="Collections" />
|
||||||
|
<PropertyColumn Property="r => r.Users" Title="Users" />
|
||||||
|
<ActionColumn>
|
||||||
|
<Space Style="display: flex; justify-content: end">
|
||||||
|
<SpaceItem>
|
||||||
|
@if (context.Name != "Administrator")
|
||||||
|
{
|
||||||
|
<Popconfirm OnConfirm="() => DeleteRole(context)" Title="Are you sure you want to delete this role?">
|
||||||
|
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
|
||||||
|
</Popconfirm>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<Tooltip Title="The administrator role cannot be deleted.">
|
||||||
|
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Disabled />
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
</SpaceItem>
|
||||||
|
</Space>
|
||||||
|
</ActionColumn>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Modal Title="Add a Role" @bind-Visible="AddRoleDialogVisible" OnOk="AddRole" OnCancel="() => AddRoleDialogVisible = false">
|
||||||
|
<Form Model="NewRole">
|
||||||
|
<FormItem Label="Name">
|
||||||
|
<Input @bind-Value="@context.Name" />
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
ICollection<RoleViewModel> Roles { get; set; }
|
||||||
|
|
||||||
|
LANCommanderSettings Settings = SettingService.GetSettings();
|
||||||
|
bool Loading = true;
|
||||||
|
bool AddRoleDialogVisible = false;
|
||||||
|
|
||||||
|
RoleViewModel NewRole = new RoleViewModel();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
Roles = new List<RoleViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
await LoadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadData()
|
||||||
|
{
|
||||||
|
Roles = new List<RoleViewModel>();
|
||||||
|
|
||||||
|
foreach (var role in RoleManager.Roles)
|
||||||
|
{
|
||||||
|
var users = await UserManager.GetUsersInRoleAsync(role.Name);
|
||||||
|
|
||||||
|
Roles.Add(new RoleViewModel()
|
||||||
|
{
|
||||||
|
Name = role.Name,
|
||||||
|
Collections = role.Collections != null ? role.Collections.Count : 0,
|
||||||
|
Users = users != null ? users.Count : 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading = false;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteRole(RoleViewModel roleViewModel)
|
||||||
|
{
|
||||||
|
var role = await RoleManager.FindByNameAsync(roleViewModel.Name);
|
||||||
|
|
||||||
|
if (role.Name == "Administrator")
|
||||||
|
{
|
||||||
|
await MessageService.Error("Cannot delete the administrator role!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await RoleManager.DeleteAsync(role);
|
||||||
|
await LoadData();
|
||||||
|
|
||||||
|
await MessageService.Success($"Deleted {role.Name}!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowNewRoleDialog()
|
||||||
|
{
|
||||||
|
NewRole = new RoleViewModel();
|
||||||
|
|
||||||
|
AddRoleDialogVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddRole()
|
||||||
|
{
|
||||||
|
if (await RoleManager.RoleExistsAsync(NewRole.Name))
|
||||||
|
{
|
||||||
|
MessageService.Error("A role with that name already exists!");
|
||||||
|
AddRoleDialogVisible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await RoleManager.CreateAsync(new Role()
|
||||||
|
{
|
||||||
|
Name = NewRole.Name
|
||||||
|
});
|
||||||
|
|
||||||
|
await LoadData();
|
||||||
|
MessageService.Success("Role added!");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageService.Error("Could not added role!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
<Menu Mode=@MenuMode.Inline Style="height: 100%">
|
<Menu Mode=@MenuMode.Inline Style="height: 100%">
|
||||||
<MenuItem RouterLink="/Settings/General">General</MenuItem>
|
<MenuItem RouterLink="/Settings/General">General</MenuItem>
|
||||||
<MenuItem RouterLink="/Settings/Users">Users</MenuItem>
|
<MenuItem RouterLink="/Settings/Users">Users</MenuItem>
|
||||||
|
<MenuItem RouterLink="/Settings/Roles">Roles</MenuItem>
|
||||||
<MenuItem RouterLink="/Settings/Authentication">Authentication</MenuItem>
|
<MenuItem RouterLink="/Settings/Authentication">Authentication</MenuItem>
|
||||||
<MenuItem RouterLink="/Settings/UserSaves">User Saves</MenuItem>
|
<MenuItem RouterLink="/Settings/UserSaves">User Saves</MenuItem>
|
||||||
<MenuItem RouterLink="/Settings/Archives">Archives</MenuItem>
|
<MenuItem RouterLink="/Settings/Archives">Archives</MenuItem>
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
|
|
||||||
<SpaceItem>
|
<SpaceItem>
|
||||||
<Tooltip Title="Change Password">
|
<Tooltip Title="Change Password">
|
||||||
<Button Icon="@IconType.Outline.Key" Type="@ButtonType.Text" OnClick="() => ChangePassword(context)" />
|
<Button Icon="@IconType.Outline.Lock" Type="@ButtonType.Text" OnClick="() => ChangePassword(context)" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</SpaceItem>
|
</SpaceItem>
|
||||||
|
|
||||||
|
|
|
@ -133,6 +133,7 @@ namespace LANCommander
|
||||||
builder.Services.AddScoped<SettingService>();
|
builder.Services.AddScoped<SettingService>();
|
||||||
builder.Services.AddScoped<ArchiveService>();
|
builder.Services.AddScoped<ArchiveService>();
|
||||||
builder.Services.AddScoped<CategoryService>();
|
builder.Services.AddScoped<CategoryService>();
|
||||||
|
builder.Services.AddScoped<CollectionService>();
|
||||||
builder.Services.AddScoped<GameService>();
|
builder.Services.AddScoped<GameService>();
|
||||||
builder.Services.AddScoped<ScriptService>();
|
builder.Services.AddScoped<ScriptService>();
|
||||||
builder.Services.AddScoped<GenreService>();
|
builder.Services.AddScoped<GenreService>();
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
using LANCommander.Data;
|
||||||
|
using LANCommander.Data.Models;
|
||||||
|
|
||||||
|
namespace LANCommander.Services
|
||||||
|
{
|
||||||
|
public class CollectionService : BaseDatabaseService<Collection>
|
||||||
|
{
|
||||||
|
public CollectionService(DatabaseContext dbContext, IHttpContextAccessor httpContextAccessor) : base(dbContext, httpContextAccessor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@
|
||||||
@if (User != null && User.IsInRole("Administrator"))
|
@if (User != null && User.IsInRole("Administrator"))
|
||||||
{
|
{
|
||||||
<MenuItem RouterLink="/Games">Games</MenuItem>
|
<MenuItem RouterLink="/Games">Games</MenuItem>
|
||||||
|
<MenuItem RouterLink="/Collections">Collections</MenuItem>
|
||||||
<MenuItem RouterLink="/Redistributables">Redistributables</MenuItem>
|
<MenuItem RouterLink="/Redistributables">Redistributables</MenuItem>
|
||||||
<MenuItem RouterLink="/Servers">Servers</MenuItem>
|
<MenuItem RouterLink="/Servers">Servers</MenuItem>
|
||||||
<MenuItem RouterLink="/Files">Files</MenuItem>
|
<MenuItem RouterLink="/Files">Files</MenuItem>
|
||||||
|
|
Loading…
Reference in New Issue