├── proxmox-cloud
├── Pages
│ ├── _ViewStart.cshtml
│ ├── _ViewImports.cshtml
│ ├── Shared
│ │ ├── _LoginPartial.cshtml
│ │ └── _Layout.cshtml
│ └── Error.cshtml
├── Areas
│ ├── Admin
│ │ ├── Pages
│ │ │ ├── _ViewStart.cshtml
│ │ │ ├── System
│ │ │ │ ├── _ViewImports.cshtml
│ │ │ │ ├── Images.cshtml
│ │ │ │ ├── Index.cshtml
│ │ │ │ ├── Networks.cshtml
│ │ │ │ ├── Volumes.cshtml
│ │ │ │ ├── Defaults.cshtml
│ │ │ │ ├── Instances.cshtml
│ │ │ │ ├── SystemInfo.cshtml
│ │ │ │ ├── _Layout.cshtml
│ │ │ │ ├── Images.cshtml.cs
│ │ │ │ ├── Index.cshtml.cs
│ │ │ │ ├── Defaults.cshtml.cs
│ │ │ │ ├── Flavors.cshtml.cs
│ │ │ │ ├── Volumes.cshtml.cs
│ │ │ │ ├── Instances.cshtml.cs
│ │ │ │ ├── SystemInfo.cshtml.cs
│ │ │ │ ├── Networks.cshtml.cs
│ │ │ │ ├── Flavors.cshtml
│ │ │ │ ├── _SystemNav.cshtml
│ │ │ │ ├── Hypervisors.cshtml.cs
│ │ │ │ ├── Hypervisors.cshtml
│ │ │ │ └── SystemNavPages.cs
│ │ │ ├── Identity
│ │ │ │ ├── _ViewImports.cshtml
│ │ │ │ ├── _Layout.cshtml
│ │ │ │ └── IdentityNavPages.cs
│ │ │ └── _ViewImports.cshtml
│ │ └── ProjectHostingStartup.cs
│ ├── Identity
│ │ ├── Pages
│ │ │ ├── Account
│ │ │ │ ├── Logout.cshtml
│ │ │ │ ├── Register.cshtml
│ │ │ │ ├── _ViewImports.cshtml
│ │ │ │ ├── RegisterConfirmation.cshtml
│ │ │ │ ├── Register.cshtml.cs
│ │ │ │ ├── RegisterConfirmation.cshtml.cs
│ │ │ │ ├── Logout.cshtml.cs
│ │ │ │ ├── Login.cshtml
│ │ │ │ └── Login.cshtml.cs
│ │ │ ├── _ViewStart.cshtml
│ │ │ ├── _ViewImports.cshtml
│ │ │ └── _ValidationScriptsPartial.cshtml
│ │ └── IdentityHostingStartup.cs
│ └── Project
│ │ ├── Pages
│ │ ├── _ViewStart.cshtml
│ │ ├── Compute
│ │ │ ├── _ViewImports.cshtml
│ │ │ ├── Index.cshtml
│ │ │ ├── _Layout.cshtml
│ │ │ └── Index.cshtml.cs
│ │ ├── Volumes
│ │ │ ├── _ViewImports.cshtml
│ │ │ ├── Index.cshtml
│ │ │ ├── _Layout.cshtml
│ │ │ └── Index.cshtml.cs
│ │ ├── Networking
│ │ │ ├── _ViewImports.cshtml
│ │ │ ├── Index.cshtml
│ │ │ ├── _Layout.cshtml
│ │ │ └── Index.cshtml.cs
│ │ ├── Index.cshtml
│ │ ├── _ViewImports.cshtml
│ │ ├── _Layout.cshtml
│ │ ├── Shared
│ │ │ └── _Layout.cshtml
│ │ └── Index.cshtml.cs
│ │ └── ProjectHostingStartup.cs
├── wwwroot
│ ├── favicon.ico
│ ├── js
│ │ └── site.js
│ └── css
│ │ └── site.css
├── appsettings.Development.json
├── ProxmoxApi
│ ├── VNetModel.cs
│ ├── IPveClient.RequestTypes.cs
│ ├── PveClientFactory.cs
│ ├── ZoneModel.cs
│ ├── HypervisorModel.cs
│ ├── IPveClient.cs
│ └── IPveClient.ResponseTypes.cs
├── Models
│ └── ErrorViewModel.cs
├── CephApi
│ └── CephClient.cs
├── libman.json
├── appsettings.json
├── Services
│ ├── ProxmoxLoadBalancer.cs
│ ├── DbMigrationService.cs
│ ├── CephAuthenticator.cs
│ ├── ProxmoxAuthenticator.cs
│ ├── AdminUserService.cs
│ ├── ProxmoxHostProvider.cs
│ └── ProxmoxScraper.cs
├── System
│ └── Threading
│ │ └── ReaderWriterLockSlimExtensions.cs
├── Properties
│ └── launchSettings.json
├── Program.cs
├── Microsoft
│ └── EntityFrameworkCore
│ │ └── RelationalPropertyBuilderExtensions.cs
├── proxmox-cloud.csproj
├── Extensions.cs
├── Data
│ ├── ApplicationDbContext.cs
│ ├── Migrations
│ │ ├── 00000000000000_CreateIdentitySchema.cs
│ │ ├── 00000000000000_CreateIdentitySchema.Designer.cs
│ │ ├── 20211008225618_OpenStackSandbox.cs
│ │ └── ApplicationDbContextModelSnapshot.cs
│ └── Models.cs
└── Startup.cs
├── .config
└── dotnet-tools.json
├── proxmox-cloud.sln
├── README.md
├── .gitignore
└── LICENSE
/proxmox-cloud/Pages/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/Account/Logout.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model LogoutModel
3 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/Account/Register.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model RegisterModel
3 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using proxmox_cloud.Areas.Admin.Pages.System
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/Identity/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using proxmox_cloud.Areas.Admin.Pages.Identity
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/Account/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using proxmox_cloud.Areas.Identity.Pages.Account
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Compute/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using proxmox_cloud.Areas.Project.Pages.Compute
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Volumes/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using proxmox_cloud.Areas.Project.Pages.Volumes
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model RegisterConfirmationModel
3 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "/Pages/Shared/_Layout.cshtml";
3 | }
4 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Networking/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using proxmox_cloud.Areas.Project.Pages.Networking
--------------------------------------------------------------------------------
/proxmox-cloud/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AliveDevil/proxmox-cloud/HEAD/proxmox-cloud/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Images.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model ImagesModel
3 | @{
4 | ViewData["Title"] = "Images";
5 | }
6 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Index.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model IndexModel
3 | @{
4 | ViewData["Title"] = "Overview";
5 | }
6 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Compute/Index.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model IndexModel
3 | @{
4 | ViewData["Title"] = "Project";
5 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Networking/Index.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model IndexModel
3 | @{
4 | ViewData["Title"] = "Project";
5 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Volumes/Index.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model IndexModel
3 | @{
4 | ViewData["Title"] = "Project";
5 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Networks.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model NetworksModel
3 | @{
4 | ViewData["Title"] = "Networks";
5 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Volumes.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model VolumesModel
3 | @{
4 | ViewData["Title"] = "Volumes";
5 | }
6 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Defaults.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model DefaultsModel
3 | @{
4 | ViewData["Title"] = "Defaults";
5 | }
6 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Instances.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model InstancesModel
3 | @{
4 | ViewData["Title"] = "Instances";
5 | }
6 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/SystemInfo.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model SystemInfoModel
3 | @{
4 | ViewData["Title"] = "System Info";
5 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Index.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @using proxmox_cloud.Areas.Project.Pages
3 | @model IndexModel
4 | @{
5 | ViewData["Title"] = "Project";
6 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using proxmox_cloud.Areas.Project
2 | @using Humanizer
3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4 |
--------------------------------------------------------------------------------
/proxmox-cloud/Pages/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using proxmox_cloud
2 | @using proxmox_cloud.Models
3 | @using Humanizer
4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
5 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using proxmox_cloud.Areas.Admin
2 | @using proxmox_cloud.Areas.Admin.Pages
3 | @using Humanizer
4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
5 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "/Pages/Shared/_Layout.cshtml";
3 | }
4 |
5 | @RenderBody()
6 |
7 | @section Scripts {
8 | @RenderSection("Scripts", required: false)
9 | }
10 |
--------------------------------------------------------------------------------
/proxmox-cloud/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Identity
2 | @using proxmox_cloud.Areas.Identity
3 | @using proxmox_cloud.Areas.Identity.Pages
4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
5 |
--------------------------------------------------------------------------------
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "dotnet-aspnet-codegenerator": {
6 | "version": "5.0.2",
7 | "commands": [
8 | "dotnet-aspnet-codegenerator"
9 | ]
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/proxmox-cloud/ProxmoxApi/VNetModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace proxmox_cloud.ProxmoxApi
7 | {
8 | public class VNetModel
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/proxmox-cloud/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | // for details on configuring this project to bundle and minify static web assets.
3 |
4 | // Write your JavaScript code.
5 |
--------------------------------------------------------------------------------
/proxmox-cloud/Models/ErrorViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace proxmox_cloud.Models
4 | {
5 | public class ErrorViewModel
6 | {
7 | public string RequestId { get; set; }
8 |
9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/proxmox-cloud/CephApi/CephClient.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 |
3 | namespace proxmox_cloud.CephApi
4 | {
5 | public class CephClient
6 | {
7 | private readonly HttpClient httpClient;
8 |
9 | public CephClient(HttpClient _httpClient)
10 | {
11 | httpClient = _httpClient;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "../_Layout.cshtml";
3 | }
4 |
5 |
6 |
7 | @RenderSection("Tabs")
8 |
9 |
10 | @RenderBody()
11 |
12 |
13 |
14 | @section Scripts {
15 | @RenderSection("Scripts", required: false)
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/Account/Register.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.AspNetCore.Mvc.RazorPages;
4 |
5 | namespace proxmox_cloud.Areas.Identity.Pages.Account
6 | {
7 | [AllowAnonymous]
8 | public class RegisterModel : PageModel
9 | {
10 | public IActionResult OnGet() => RedirectToPage("Login");
11 | }
12 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Compute/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "../Shared/_Layout.cshtml";
3 | }
4 |
5 | @RenderBody()
6 |
7 | @section Tabs {
8 |
13 | }
14 |
15 | @section Scripts {
16 | @RenderSection("Scripts", required: false)
17 | }
18 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Volumes/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "../Shared/_Layout.cshtml";
3 | }
4 |
5 | @RenderBody()
6 |
7 | @section Tabs {
8 |
13 | }
14 |
15 | @section Scripts {
16 | @RenderSection("Scripts", required: false)
17 | }
18 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Networking/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "../Shared/_Layout.cshtml";
3 | }
4 |
5 | @RenderBody()
6 |
7 | @section Tabs {
8 |
13 | }
14 |
15 | @section Scripts {
16 | @RenderSection("Scripts", required: false)
17 | }
18 |
--------------------------------------------------------------------------------
/proxmox-cloud/ProxmoxApi/IPveClient.RequestTypes.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System.Collections.Generic;
3 |
4 | namespace proxmox_cloud.ProxmoxApi
5 | {
6 | public abstract class JsonRequest
7 | {
8 | [JsonExtensionData]
9 | public Dictionary AdditionalProperties { get; } = new();
10 | }
11 |
12 | public sealed class NodeConfigRequest : JsonRequest
13 | {
14 | }
15 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Index.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Project.Pages
9 | {
10 | public class IndexModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "/Pages/Shared/_Layout.cshtml";
3 | }
4 |
5 | System
6 |
7 |
8 |
11 |
12 |
@(ViewData["Title"])
13 | @RenderBody()
14 |
15 |
16 |
17 | @section Scripts {
18 | @RenderSection("Scripts", required: false)
19 | }
20 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.AspNetCore.Mvc;
3 | using Microsoft.AspNetCore.Mvc.RazorPages;
4 |
5 | namespace proxmox_cloud.Areas.Identity.Pages.Account
6 | {
7 | [AllowAnonymous]
8 | public class RegisterConfirmationModel : PageModel
9 | {
10 | public IActionResult OnGet() => RedirectToPage("Login");
11 | }
12 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/Identity/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "/Pages/Shared/_Layout.cshtml";
3 | }
4 |
5 | Identity
6 |
7 |
8 |
11 |
12 |
@(ViewData["Title"])
13 | @RenderBody()
14 |
15 |
16 |
17 | @section Scripts {
18 | @RenderSection("Scripts", required: false)
19 | }
20 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Images.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Admin.Pages.System
9 | {
10 | public class ImagesModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Index.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Admin.Pages.System
9 | {
10 | public class IndexModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Defaults.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Admin.Pages.System
9 | {
10 | public class DefaultsModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Flavors.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Admin.Pages.System
9 | {
10 | public class FlavorsModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Volumes.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Admin.Pages.System
9 | {
10 | public class VolumesModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Compute/Index.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Project.Pages.Compute
9 | {
10 | public class IndexModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Volumes/Index.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Project.Pages.Volumes
9 | {
10 | public class IndexModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Instances.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Admin.Pages.System
9 | {
10 | public class InstancesModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/SystemInfo.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Admin.Pages.System
9 | {
10 | public class SystemInfoModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/Pages/Networking/Index.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Mvc.RazorPages;
7 |
8 | namespace proxmox_cloud.Areas.Project.Pages.Networking
9 | {
10 | public class IndexModel : PageModel
11 | {
12 | public void OnGet()
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/ProjectHostingStartup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 |
3 | [assembly: HostingStartup(typeof(proxmox_cloud.Areas.Admin.AdminHostingStartup))]
4 |
5 | namespace proxmox_cloud.Areas.Admin
6 | {
7 | public class AdminHostingStartup : IHostingStartup
8 | {
9 | public void Configure(IWebHostBuilder builder)
10 | {
11 | builder.ConfigureServices((context, services) =>
12 | {
13 | });
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Project/ProjectHostingStartup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 |
3 | [assembly: HostingStartup(typeof(proxmox_cloud.Areas.Project.ProjectHostingStartup))]
4 |
5 | namespace proxmox_cloud.Areas.Project
6 | {
7 | public class ProjectHostingStartup : IHostingStartup
8 | {
9 | public void Configure(IWebHostBuilder builder)
10 | {
11 | builder.ConfigureServices((context, services) =>
12 | {
13 | });
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/IdentityHostingStartup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 |
3 | [assembly: HostingStartup(typeof(proxmox_cloud.Areas.Identity.IdentityHostingStartup))]
4 |
5 | namespace proxmox_cloud.Areas.Identity
6 | {
7 | public class IdentityHostingStartup : IHostingStartup
8 | {
9 | public void Configure(IWebHostBuilder builder)
10 | {
11 | builder.ConfigureServices((context, services) =>
12 | {
13 | });
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Networks.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc.RazorPages;
2 | using proxmox_cloud.ProxmoxApi;
3 | using System.Threading.Tasks;
4 |
5 | namespace proxmox_cloud.Areas.Admin.Pages.System
6 | {
7 | public class NetworksModel : PageModel
8 | {
9 | private readonly PveClientFactory pveClient;
10 |
11 | public NetworksModel(PveClientFactory pveClient)
12 | {
13 | this.pveClient = pveClient;
14 | }
15 |
16 | public void OnGet()
17 | {
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/proxmox-cloud/libman.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0",
3 | "defaultProvider": "jsdelivr",
4 | "libraries": [
5 | {
6 | "destination": "wwwroot/lib/bootstrap/",
7 | "files": [
8 | "dist/css/bootstrap.css",
9 | "dist/css/bootstrap.css.map",
10 | "dist/css/bootstrap.min.css",
11 | "dist/css/bootstrap.min.css.map",
12 | "dist/js/bootstrap.bundle.js",
13 | "dist/js/bootstrap.bundle.js.map",
14 | "dist/js/bootstrap.bundle.min.js",
15 | "dist/js/bootstrap.bundle.min.js.map"
16 | ],
17 | "library": "bootstrap@5.1.1"
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/Identity/IdentityNavPages.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc.Rendering;
2 | using System;
3 | using static System.IO.Path;
4 |
5 | namespace proxmox_cloud.Areas.Admin.Pages.Identity
6 | {
7 | public class IdentityNavPages
8 | {
9 | private static string PageNavClass(ViewContext viewContext, string page)
10 | {
11 | var activePage = viewContext.ViewData["ActivePage"] as string
12 | ?? GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
13 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/proxmox-cloud/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "DefaultConnection": "DataSource=app.db;Cache=Shared"
4 | },
5 | "Logging": {
6 | "LogLevel": {
7 | "Default": "Information",
8 | "Microsoft": "Warning",
9 | "Microsoft.Hosting.Lifetime": "Information"
10 | }
11 | },
12 | "AllowedHosts": "*",
13 | "Ceph": {
14 | "Monitors": [],
15 | "Service": {
16 | "username": "proxmox-cloud",
17 | "password": "Proxmox"
18 | }
19 | },
20 | "Proxmox": {
21 | "Cluster": {
22 | "Hosts": []
23 | },
24 | "Api": {
25 | "LoadBalance": "RoundRobin"
26 | },
27 | "Service": {
28 | "ApiToken": "",
29 | "ApiSecret": ""
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/proxmox-cloud/Services/ProxmoxLoadBalancer.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Hosting;
2 | using proxmox_cloud.ProxmoxApi;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace proxmox_cloud.Services
7 | {
8 | public class ProxmoxLoadBalancer : IHostedService
9 | {
10 | private readonly PveClientFactory clientFactory;
11 |
12 | public ProxmoxLoadBalancer(PveClientFactory clientFactory)
13 | {
14 | this.clientFactory = clientFactory;
15 | }
16 |
17 | public Task StartAsync(CancellationToken cancellationToken)
18 | {
19 | return Task.CompletedTask;
20 | }
21 |
22 | public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
23 | }
24 | }
--------------------------------------------------------------------------------
/proxmox-cloud/System/Threading/ReaderWriterLockSlimExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 |
4 | namespace proxmox_cloud.System.Threading
5 | {
6 | public static class ReaderWriterLockSlimExtensions
7 | {
8 | public static Scope UseReadLock(this ReaderWriterLockSlim @this) => new(@this.EnterReadLock, @this.ExitReadLock);
9 |
10 | public static Scope UseWriteLock(this ReaderWriterLockSlim @this) => new(@this.EnterWriteLock, @this.ExitWriteLock);
11 |
12 | public ref struct Scope
13 | {
14 | private readonly Action release;
15 |
16 | public Scope(Action enter, Action release)
17 | {
18 | this.release = release;
19 | enter();
20 | }
21 |
22 | public void Dispose() => release();
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Pages/Shared/_LoginPartial.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Identity
2 | @inject SignInManager SignInManager
3 | @inject UserManager UserManager
4 |
5 |
--------------------------------------------------------------------------------
/proxmox-cloud/Services/DbMigrationService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.Extensions.Hosting;
3 | using proxmox_cloud.Data;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace proxmox_cloud.Services
8 | {
9 | public class DbMigrationService : IHostedService
10 | {
11 | private readonly IDbContextFactory contextFactory;
12 |
13 | public DbMigrationService(IDbContextFactory contextFactory)
14 | {
15 | this.contextFactory = contextFactory;
16 | }
17 |
18 | public async Task StartAsync(CancellationToken cancellationToken)
19 | {
20 | using var context = contextFactory.CreateDbContext();
21 | await context.Database.MigrateAsync();
22 | }
23 |
24 | public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
25 | }
26 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Flavors.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model FlavorsModel
3 | @{
4 | ViewData["Title"] = "Flavors";
5 | }
6 |
7 | Flavors
8 |
9 |
14 |
15 |
16 |
17 |
18 | Flavor Name
19 | vCPUs
20 | Memory
21 | Root Disk
22 | Ephemeral Disk
23 | Swap Disk
24 | ID
25 | Public
26 | Metadata
27 | Actions
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Displaying x items
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/proxmox-cloud/Pages/Error.cshtml:
--------------------------------------------------------------------------------
1 | @model ErrorViewModel
2 | @{
3 | ViewData["Title"] = "Error";
4 | }
5 |
6 | Error.
7 | An error occurred while processing your request.
8 |
9 | @if (Model.ShowRequestId)
10 | {
11 |
12 | Request ID: @Model.RequestId
13 |
14 | }
15 |
16 | Development Mode
17 |
18 | Swapping to Development environment will display more detailed information about the error that occurred.
19 |
20 |
21 | The Development environment shouldn't be enabled for deployed applications.
22 | It can result in displaying sensitive information from exceptions to end users.
23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
24 | and restarting the app.
25 |
26 |
--------------------------------------------------------------------------------
/proxmox-cloud/Services/CephAuthenticator.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace proxmox_cloud.Services
6 | {
7 | public class CephAuthenticator
8 | {
9 | public async Task AuthenticateAsync(HttpRequestMessage request)
10 | {
11 | }
12 |
13 | public class Handler : DelegatingHandler
14 | {
15 | private readonly CephAuthenticator authenticator;
16 |
17 | public Handler(CephAuthenticator authenticator)
18 | {
19 | this.authenticator = authenticator;
20 | }
21 |
22 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
23 | {
24 | await authenticator.AuthenticateAsync(request);
25 |
26 | return await base.SendAsync(request, cancellationToken);
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:39525",
7 | "sslPort": 44314
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development",
16 | "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
17 | }
18 | },
19 | "proxmox_cloud": {
20 | "commandName": "Project",
21 | "dotnetRunMessages": "true",
22 | "launchBrowser": true,
23 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
24 | "environmentVariables": {
25 | "ASPNETCORE_ENVIRONMENT": "Development",
26 | "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/proxmox-cloud/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Hosting;
4 | using proxmox_cloud.Services;
5 |
6 | namespace proxmox_cloud
7 | {
8 | public class Program
9 | {
10 | public static IHostBuilder CreateHostBuilder(string[] args) =>
11 | Host.CreateDefaultBuilder(args)
12 | .ConfigureServices(services =>
13 | {
14 | services.AddHostedService();
15 | services.AddHostedService();
16 | services.AddHostedService();
17 | services.AddHostedService(services => services.GetService());
18 | })
19 | .ConfigureWebHostDefaults(webBuilder =>
20 | {
21 | webBuilder.UseStartup();
22 | });
23 |
24 | public static void Main(string[] args)
25 | {
26 | CreateHostBuilder(args).Build().Run();
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Services/ProxmoxAuthenticator.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using System.Net;
3 | using System.Net.Http;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace proxmox_cloud.Services
8 | {
9 | public class ProxmoxAuthenticator : DelegatingHandler
10 | {
11 | private readonly NetworkCredential serviceCredential;
12 |
13 | public ProxmoxAuthenticator(IConfiguration configuration)
14 | {
15 | var serviceSection = configuration.GetSection("Proxmox").GetSection("Service");
16 | serviceCredential = new(
17 | serviceSection.GetValue("ApiToken"),
18 | serviceSection.GetValue("ApiSecret"));
19 | }
20 |
21 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
22 | {
23 | request.Headers.Authorization = new("PVEAPIToken",
24 | serviceCredential.UserName + "=" + serviceCredential.Password);
25 |
26 | return base.SendAsync(request, cancellationToken);
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/proxmox-cloud/ProxmoxApi/PveClientFactory.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using proxmox_cloud.Services;
3 | using System;
4 |
5 | namespace proxmox_cloud.ProxmoxApi
6 | {
7 | public sealed class PveClientFactory
8 | {
9 | private readonly ProxmoxHostProvider hostProvider;
10 | private readonly IServiceProvider provider;
11 |
12 | public PveClientFactory(ProxmoxHostProvider hostProvider, IServiceProvider provider)
13 | {
14 | this.hostProvider = hostProvider;
15 | this.provider = provider;
16 | }
17 |
18 | public IPveClient Get()
19 | {
20 | var client = provider.GetService();
21 | client.Client.BaseAddress = new Uri(hostProvider.Get(), "/api2/json");
22 | return client;
23 | }
24 |
25 | public IPveClient Get(string hostname)
26 | {
27 | var client = provider.GetService();
28 | UriBuilder urlBuilder = new("https", hostname, 8006, "/api2/json");
29 | client.Client.BaseAddress = urlBuilder.Uri;
30 | return client;
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/_SystemNav.cshtml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/proxmox-cloud/ProxmoxApi/ZoneModel.cs:
--------------------------------------------------------------------------------
1 | namespace proxmox_cloud.ProxmoxApi
2 | {
3 | public class ZoneModel
4 | {
5 | public ZoneModel(ClusterResource resource)
6 | {
7 | Zone = (string)resource.AdditionalProperties["sdn"];
8 | }
9 |
10 | public ZoneModel(ZoneResponse zone)
11 | {
12 | DNS = zone.DNS;
13 | DNSZone = zone.DNSZone;
14 | IPAM = zone.IPAM;
15 | MTU = zone.MTU;
16 | Nodes = zone.Nodes;
17 | Pending = zone.Pending;
18 | ReverseDNS = zone.ReverseDNS;
19 | State = zone.State;
20 | Type = zone.Type;
21 | Zone = zone.Zone;
22 | }
23 |
24 | public string DNS { get; }
25 |
26 | public string DNSZone { get; }
27 |
28 | public string IPAM { get; }
29 |
30 | public int MTU { get; }
31 |
32 | public string Nodes { get; }
33 |
34 | public bool Pending { get; }
35 |
36 | public string ReverseDNS { get; }
37 |
38 | public string State { get; }
39 |
40 | public SDNZoneType Type { get; }
41 |
42 | public string Zone { get; }
43 | }
44 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
18 |
19 |
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/Account/Logout.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.AspNetCore.Identity;
3 | using Microsoft.AspNetCore.Mvc;
4 | using Microsoft.AspNetCore.Mvc.RazorPages;
5 | using Microsoft.Extensions.Logging;
6 | using System.Threading.Tasks;
7 |
8 | namespace proxmox_cloud.Areas.Identity.Pages.Account
9 | {
10 | [AllowAnonymous]
11 | public class LogoutModel : PageModel
12 | {
13 | private readonly ILogger _logger;
14 | private readonly SignInManager _signInManager;
15 |
16 | public LogoutModel(SignInManager signInManager, ILogger logger)
17 | {
18 | _signInManager = signInManager;
19 | _logger = logger;
20 | }
21 |
22 | public IActionResult OnGet() => RedirectToPage("Login");
23 |
24 | public async Task OnPost(string returnUrl = null)
25 | {
26 | await _signInManager.SignOutAsync();
27 | _logger.LogInformation("User logged out.");
28 | if (returnUrl != null)
29 | {
30 | return LocalRedirect(returnUrl);
31 | }
32 | else
33 | {
34 | return RedirectToPage();
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Hypervisors.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.AspNetCore.Mvc.RazorPages;
3 | using proxmox_cloud.Services;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | namespace proxmox_cloud.Areas.Admin.Pages.System
8 | {
9 | public class HypervisorsModel : PageModel
10 | {
11 | private readonly ProxmoxScraper client;
12 |
13 | public HypervisorsModel(ProxmoxScraper client)
14 | {
15 | this.client = client;
16 | }
17 |
18 | public double CPU { get; set; }
19 |
20 | public List Hypervisors { get; set; }
21 |
22 | public double MaxCPU { get; set; }
23 |
24 | public long MaxMemory { get; set; }
25 |
26 | public long Memory { get; set; }
27 |
28 | public IActionResult OnGetAsync()
29 | {
30 | client.Collect((in ProxmoxScraper.CollectContext c) =>
31 | {
32 | Hypervisors = c.Hypervisors.Select(r => new HypervisorModel(
33 | r.Node, r.MaxCpu, r.Cpu, r.MaxMem, r.Mem, 0)).ToList();
34 | });
35 |
36 | return Page();
37 | }
38 |
39 | public record HypervisorModel(string Hostname, double MaxCPU, double CPU, long MaxMemory, long Memory, int Instances);
40 | }
41 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/Hypervisors.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model HypervisorsModel
3 | @{
4 | ViewData["Title"] = "All Hypervisors";
5 | }
6 |
7 | Summary
8 |
9 | CPU: Used @(Model.CPU.Hundreds().ToString("#"))% of @Model.MaxCPU CPUs
10 |
11 |
12 | Memory: Used @Model.Memory.Bytes().ToString("#") of @Model.MaxMemory.Bytes().ToString("#")
13 |
14 |
15 | Hypervisors
16 |
17 |
18 |
19 | Hostname
20 | CPUs (total)
21 | CPUs (used)
22 | Memory (total)
23 | Memory (used)
24 | Instances
25 |
26 |
27 |
28 | @foreach (var item in Model.Hypervisors)
29 | {
30 |
31 | @item.Hostname
32 | @item.MaxCPU
33 | @item.CPU.Hundreds().ToString("#")%
34 | @item.MaxMemory.Bytes().ToString("#")
35 | @item.Memory.Bytes().ToString("#")
36 | @item.Instances
37 |
38 | }
39 |
40 |
41 |
42 | Displaying @Model.Hypervisors.Count items
43 |
44 |
45 |
--------------------------------------------------------------------------------
/proxmox-cloud/ProxmoxApi/HypervisorModel.cs:
--------------------------------------------------------------------------------
1 | namespace proxmox_cloud.ProxmoxApi
2 | {
3 | public class HypervisorModel
4 | {
5 | public HypervisorModel(ClusterResource resource)
6 | {
7 | Disk = resource.Disk;
8 | Id = resource.Id;
9 | MaxDisk = resource.MaxDisk;
10 | MaxMem = resource.MaxMem;
11 | Mem = resource.Mem;
12 | Node = resource.Node;
13 | }
14 |
15 | public HypervisorModel(NodeResponse node)
16 | {
17 | Cpu = node.Cpu;
18 | Disk = node.Disk;
19 | Id = node.Id;
20 | Level = node.Level;
21 | MaxCpu = node.MaxCpu;
22 | MaxDisk = node.MaxDisk;
23 | MaxMem = node.MaxMem;
24 | Mem = node.Mem;
25 | Node = node.Node;
26 | SSLFingerprint = node.SSLFingerprint;
27 | Status = node.Status;
28 | UpTime = node.UpTime;
29 | }
30 |
31 | public double Cpu { get; }
32 |
33 | public long Disk { get; }
34 |
35 | public string Id { get; }
36 |
37 | public string Level { get; }
38 |
39 | public int MaxCpu { get; }
40 |
41 | public long MaxDisk { get; }
42 |
43 | public long MaxMem { get; }
44 |
45 | public long Mem { get; }
46 |
47 | public string Node { get; }
48 |
49 | public string SSLFingerprint { get; }
50 |
51 | public NodeStatus Status { get; }
52 |
53 | public int UpTime { get; }
54 | }
55 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Microsoft/EntityFrameworkCore/RelationalPropertyBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Microsoft.EntityFrameworkCore.Metadata;
3 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
4 |
5 | // sources:
6 | // * TimestampMixin / SoftDeleteMixin: https://opendev.org/openstack/oslo.db/src/branch/master/oslo_db/sqlalchemy/models.py
7 | // * Identity: https://opendev.org/openstack/keystone/src/branch/master/keystone/identity/backends/sql_model.py
8 | // * Images: https://opendev.org/openstack/glance/src/branch/master/glance/db/sqlalchemy/models.py
9 | // * Volumes: https://opendev.org/openstack/cinder/src/branch/master/cinder/db/sqlalchemy/models.py
10 | // * Instances: https://opendev.org/openstack/nova/src/branch/master/nova/db/main/models.py
11 | // * Instances 2: https://opendev.org/openstack/nova/src/branch/master/nova/db/api/models.py
12 | namespace proxmox_cloud.Microsoft.EntityFrameworkCore
13 | {
14 | public static class RelationalPropertyBuilderExtensions
15 | {
16 | public static PropertyBuilder ValueGeneration(this PropertyBuilder @this, ValueGenerated generated)
17 | {
18 | @this.Metadata.ValueGenerated = generated;
19 | if ((generated & ValueGenerated.OnAddOrUpdate) > 0)
20 | {
21 | @this.Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Save);
22 | }
23 | if ((generated & ~ValueGenerated.OnAdd) > 0)
24 | {
25 | @this.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
26 | }
27 | return @this;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/proxmox-cloud/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | for details on configuring this project to bundle and minify static web assets. */
3 |
4 | a.navbar-brand {
5 | white-space: normal;
6 | text-align: center;
7 | word-break: break-all;
8 | }
9 |
10 | /* Provide sufficient contrast against white background */
11 | a {
12 | color: #0366d6;
13 | }
14 |
15 | .btn-primary {
16 | color: #fff;
17 | background-color: #1b6ec2;
18 | border-color: #1861ac;
19 | }
20 |
21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
22 | color: #fff;
23 | background-color: #1b6ec2;
24 | border-color: #1861ac;
25 | }
26 |
27 | /* Sticky footer styles
28 | -------------------------------------------------- */
29 | html {
30 | font-size: 14px;
31 | }
32 | @media (min-width: 768px) {
33 | html {
34 | font-size: 16px;
35 | }
36 | }
37 |
38 | .border-top {
39 | border-top: 1px solid #e5e5e5;
40 | }
41 | .border-bottom {
42 | border-bottom: 1px solid #e5e5e5;
43 | }
44 |
45 | .box-shadow {
46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
47 | }
48 |
49 | button.accept-policy {
50 | font-size: 1rem;
51 | line-height: inherit;
52 | }
53 |
54 | /* Sticky footer styles
55 | -------------------------------------------------- */
56 | html {
57 | position: relative;
58 | min-height: 100%;
59 | }
60 |
61 | body {
62 | /* Margin bottom by footer height */
63 | margin-bottom: 60px;
64 | }
65 | .footer {
66 | position: absolute;
67 | bottom: 0;
68 | width: 100%;
69 | white-space: nowrap;
70 | line-height: 60px; /* Vertically center the text there */
71 | }
72 |
--------------------------------------------------------------------------------
/proxmox-cloud/proxmox-cloud.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net5.0
4 | aspnet-proxmox_cloud-F5AEDF4C-4474-4CB9-AEB1-ECB899053A5E
5 | proxmox_cloud
6 | preview
7 | true
8 | True
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/proxmox-cloud.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "proxmox-cloud", "proxmox-cloud\proxmox-cloud.csproj", "{5C995B61-06B5-42F5-BDCF-EB8BF11FC389}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Debug|x64 = Debug|x64
12 | Debug|x86 = Debug|x86
13 | Release|Any CPU = Release|Any CPU
14 | Release|x64 = Release|x64
15 | Release|x86 = Release|x86
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Debug|x64.ActiveCfg = Debug|Any CPU
21 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Debug|x64.Build.0 = Debug|Any CPU
22 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Debug|x86.ActiveCfg = Debug|Any CPU
23 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Debug|x86.Build.0 = Debug|Any CPU
24 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Release|x64.ActiveCfg = Release|Any CPU
27 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Release|x64.Build.0 = Release|Any CPU
28 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Release|x86.ActiveCfg = Release|Any CPU
29 | {5C995B61-06B5-42F5-BDCF-EB8BF11FC389}.Release|x86.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {5BB267E9-C0A9-43CF-A3D6-B88E3E56A90C}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/proxmox-cloud/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace proxmox_cloud
6 | {
7 | public static class Extensions
8 | {
9 | public static Task WaitOneAsync(this WaitHandle handle, int millisecondsTimeout) => handle.WaitOneAsync(millisecondsTimeout, CancellationToken.None);
10 |
11 | public static async Task WaitOneAsync(this WaitHandle handle, int millisecondsTimeout, CancellationToken cancellationToken)
12 | {
13 | RegisteredWaitHandle registeredHandle = null;
14 | CancellationTokenRegistration tokenRegistration = default;
15 | try
16 | {
17 | var tcs = new TaskCompletionSource();
18 | registeredHandle = ThreadPool.RegisterWaitForSingleObject(
19 | handle,
20 | (state, timedOut) => ((TaskCompletionSource)state).TrySetResult(!timedOut),
21 | tcs,
22 | millisecondsTimeout,
23 | true);
24 | tokenRegistration = cancellationToken.Register(
25 | state => ((TaskCompletionSource)state).TrySetCanceled(),
26 | tcs);
27 | return await tcs.Task;
28 | }
29 | finally
30 | {
31 | if (registeredHandle != null)
32 | registeredHandle.Unregister(null);
33 | tokenRegistration.Dispose();
34 | }
35 | }
36 |
37 | public static Task WaitOneAsync(this WaitHandle handle, TimeSpan timeout) => handle.WaitOneAsync((int)timeout.TotalMilliseconds);
38 |
39 | public static Task WaitOneAsync(this WaitHandle handle, TimeSpan timeout, CancellationToken cancellationToken) => handle.WaitOneAsync((int)timeout.TotalMilliseconds, cancellationToken);
40 |
41 | public static Task WaitOneAsync(this WaitHandle handle) => handle.WaitOneAsync(Timeout.Infinite);
42 |
43 | public static Task WaitOneAsync(this WaitHandle handle, CancellationToken cancellationToken) => handle.WaitOneAsync(Timeout.Infinite, cancellationToken);
44 | }
45 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Admin/Pages/System/SystemNavPages.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc.Rendering;
2 | using System;
3 | using static System.IO.Path;
4 |
5 | namespace proxmox_cloud.Areas.Admin.Pages.System
6 | {
7 | public class SystemNavPages
8 | {
9 | public const string Defaults = "Defaults";
10 | public const string Flavors = "Flavors";
11 | public const string Hypervisors = "Hypervisors";
12 | public const string Images = "Images";
13 | public const string Index = "Index";
14 | public const string Instances = "Instances";
15 | public const string Networks = "Networks";
16 | public const string SystemInfo = "SystemInfo";
17 | public const string Volumes = "Volumes";
18 |
19 | public static string DefaultsNavClass(ViewContext viewContext) => PageNavClass(viewContext, Defaults);
20 |
21 | public static string FlavorsNavClass(ViewContext viewContext) => PageNavClass(viewContext, Flavors);
22 |
23 | public static string HypervisorsNavClass(ViewContext viewContext) => PageNavClass(viewContext, Hypervisors);
24 |
25 | public static string ImagesNavClass(ViewContext viewContext) => PageNavClass(viewContext, Images);
26 |
27 | public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
28 |
29 | public static string InstancesNavClass(ViewContext viewContext) => PageNavClass(viewContext, Instances);
30 |
31 | public static string NetworksNavClass(ViewContext viewContext) => PageNavClass(viewContext, Networks);
32 |
33 | public static string SystemInfoNavClass(ViewContext viewContext) => PageNavClass(viewContext, SystemInfo);
34 |
35 | public static string VolumesNavClass(ViewContext viewContext) => PageNavClass(viewContext, Volumes);
36 |
37 | private static string PageNavClass(ViewContext viewContext, string page)
38 | {
39 | var activePage = viewContext.ViewData["ActivePage"] as string
40 | ?? GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
41 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/proxmox-cloud/ProxmoxApi/IPveClient.cs:
--------------------------------------------------------------------------------
1 | using Refit;
2 | using System;
3 | using System.Net.Http;
4 | using System.Threading.Tasks;
5 |
6 | namespace proxmox_cloud.ProxmoxApi
7 | {
8 | public partial interface IPveClient : IDisposable
9 | {
10 | HttpClient Client { get; }
11 |
12 | [Get("/cluster/nextid")]
13 | Task> GetClusterNextId();
14 |
15 | [Get("/cluster/options")]
16 | Task> GetClusterOptions();
17 |
18 | [Get("/cluster/resources")]
19 | Task> GetClusterResources();
20 |
21 | [Get("/cluster/status")]
22 | Task> GetClusterStatus();
23 |
24 | [Get("/nodes/{node}")]
25 | Task> GetNode(string node);
26 |
27 | [Get("/nodes/{node}/config")]
28 | Task> GetNodeConfig(string node);
29 |
30 | [Get("/nodes")]
31 | Task> GetNodes();
32 |
33 | [Get("/nodes/{node}/version")]
34 | Task> GetNodeVersion(string node);
35 |
36 | [Get("/nodes/{node}/qemu")]
37 | Task> GetNodeVirtualMachines(string node);
38 |
39 | [Get("/pools/{pool}")]
40 | Task> GetPool(string pool);
41 |
42 | [Get("/pools")]
43 | Task> GetPools();
44 |
45 | [Get("/storage/{storage}")]
46 | Task> GetStorage(string storage);
47 |
48 | [Get("/storage")]
49 | Task> GetStorages();
50 |
51 | [Get("/version")]
52 | Task> GetVersion();
53 |
54 | [Get("/cluster/sdn/vnets/{vnet}")]
55 | Task> GetVNet(string vnet);
56 |
57 | [Get("/cluster/sdn/vnets")]
58 | Task> GetVNets();
59 |
60 | [Get("/cluster/sdn/zones/{zone}")]
61 | Task> GetZone(string zone);
62 |
63 | [Get("/cluster/sdn/zones")]
64 | Task> GetZones();
65 | }
66 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Services/AdminUserService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Hosting;
4 | using Microsoft.Extensions.Logging;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace proxmox_cloud.Services
9 | {
10 | public class AdminUserService : IHostedService
11 | {
12 | private readonly ILogger logger;
13 | private readonly IServiceScopeFactory scopeFactory;
14 |
15 | public AdminUserService(ILogger logger, IServiceScopeFactory scopeFactory)
16 | {
17 | this.logger = logger;
18 | this.scopeFactory = scopeFactory;
19 | }
20 |
21 | public async Task StartAsync(CancellationToken cancellationToken)
22 | {
23 | using var scope = scopeFactory.CreateScope();
24 | var userManager = scope.ServiceProvider.GetRequiredService>();
25 | var roleManager = scope.ServiceProvider.GetRequiredService>();
26 | var hasher = scope.ServiceProvider.GetRequiredService>();
27 |
28 | if (await roleManager.FindByNameAsync("Administrator") is not IdentityRole)
29 | {
30 | IdentityRole role = new("Administrator");
31 | if (await roleManager.CreateAsync(role) is IdentityResult result && result != IdentityResult.Success)
32 | {
33 | // TODO: Error
34 | }
35 | }
36 | var users = await userManager.GetUsersInRoleAsync("Administrator");
37 | if (users.Count == 0)
38 | {
39 | IdentityUser user = new("admin");
40 | user.PasswordHash = hasher.HashPassword(user, "Proxmox");
41 | if (await userManager.CreateAsync(user) is IdentityResult result && result != IdentityResult.Success)
42 | {
43 | // TODO: Error
44 | }
45 | logger.Log(LogLevel.Information, "PLEASE CHANGE! `admin` `password is `Proxmox`.");
46 | await userManager.AddToRoleAsync(user, "Administrator");
47 | }
48 | }
49 |
50 | public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
51 | }
52 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Proxmox Cloud
2 |
3 | ## Roadmap
4 | Implementation is against my PVE setup, expect to see, some time in the future, a capable OpenStack Horizon-alike dashboard for PVE.
5 | Non-exhaustive list of features planned, implemented, or on the radar:
6 |
7 | - Provide proper project management capabilities (detached from PAM or PVE authentication)
8 | - Projects (can be created and disposed as required)
9 | - Groups (Combine users, and assign per project privileges to groups)
10 | - Users
11 | - Requires only one pve-user for the Proxmox Cloud-VM with Administrator privileges
12 | - Cluster-wide auto migration/load balancing of VMs
13 | - LXC not supported [1]
14 | - QEMU management and user provisioning
15 | - OpenStack-like configuration:
16 | - Nova Flavors
17 | - Neutron provider, project and user-provisioned networks
18 | - Features unsupported by OpenStack (upto) Xena: QinQ over 802.1q
19 | - Glance Images
20 | - Cinder Volumes
21 | - RHV/oVirt-like global cluster options
22 | - CPU Model for VMs
23 | - Image management on Ceph RBD
24 | - This will bypass PVE ISO template management for CephFS, using straight snapshots/clones.
25 | - Storage Management, Ceph only
26 | - Volume management on Ceph RBD
27 | - Image/Template management
28 | - Using Proxmox Templates is _no_ requirement,
29 | if I can work around Proxmox Templates, I will.
30 | - HA for VMs
31 | - ? Automatic (cloud-wide)
32 | - ? Manual (configurable by user)
33 | - One-Way migration of already existing cluster resources (VM, SDN Network) to Proxmox Cloud
34 | - If possible renaming SDN Zones and VNets will happen for easier management in Proxmox Cloud
35 | - VM description containing CommonMark description
36 |
37 | Proxmox Cloud won't be a cluster management platform, as the PVE Web GUI is already well-suited for this job.
38 | This will be a self-service cloud-like platform on top of PVE.
39 |
40 | ## Help Wanted
41 | Currently I cannot accept any help, as I'm still figuring this out.
42 |
43 | Though these parts will be left open, unless I find myself needing these in my deployment:
44 | - SDN-features, except
45 | - Simple
46 | - QinQ tunneling over 802.1q
47 | - VLAN
48 | - VXLAN
49 | - Storage-management, except
50 | - CephFS (LXC Root FS)
51 | - Ceph RBD
52 | - Backup
53 | - Replication
54 | - ACME
55 | - Firewall
56 | - Permissions (unless already implemented by internal identity support)
57 |
58 | ## License
59 | Licensed under EUPL-1.2, see [LICENSE](LICENSE).
60 |
61 | [1]: https://forum.proxmox.com/threads/live-migration-lxc.38682/
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/Account/Login.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @model LoginModel
3 |
4 | @{
5 | Layout = null;
6 | ViewData["Title"] = "Log in";
7 | }
8 |
9 |
10 |
11 |
12 |
13 |
14 | @ViewData["Title"] - Proxmox Cloud
15 |
16 |
17 |
68 |
69 |
70 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/proxmox-cloud/Services/ProxmoxHostProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using System;
3 | using System.Collections.Immutable;
4 |
5 | namespace proxmox_cloud.Services
6 | {
7 | public class ProxmoxHostProvider
8 | {
9 | private readonly ImmutableArray hosts;
10 | private readonly IHostSelector selector;
11 |
12 | public ProxmoxHostProvider(IConfiguration configuration)
13 | {
14 | var proxmox = configuration.GetSection("Proxmox");
15 | var section = proxmox.GetSection("Cluster");
16 | var api = proxmox.GetSection("Api");
17 | var hosts = section.GetSection("Hosts").GetChildren();
18 |
19 | var hostsArray = ImmutableArray.CreateBuilder();
20 | foreach (var host in hosts)
21 | {
22 | if (!Uri.TryCreate(host.Value, UriKind.Absolute, out var uri))
23 | {
24 | continue;
25 | }
26 | hostsArray.Add(uri);
27 | }
28 | this.hosts = hostsArray.ToImmutable();
29 | selector = Select(api.GetValue("LoadBalance", Mode.Random), this);
30 | }
31 |
32 | public enum Mode
33 | {
34 | Random,
35 | RoundRobin
36 | }
37 |
38 | private interface IHostSelector
39 | {
40 | Uri Get();
41 | }
42 |
43 | public Uri Get() => selector.Get();
44 |
45 | private static IHostSelector Select(Mode mode, ProxmoxHostProvider provider) => mode switch
46 | {
47 | Mode.Random => new RandomHostSelector(provider),
48 | Mode.RoundRobin => new RoundRobinSelector(provider),
49 | _ => throw new NotImplementedException()
50 | };
51 |
52 | private class RandomHostSelector : IHostSelector
53 | {
54 | private readonly int mod;
55 | private readonly ProxmoxHostProvider provider;
56 | private readonly Random random = new();
57 |
58 | public RandomHostSelector(ProxmoxHostProvider provider)
59 | {
60 | this.provider = provider;
61 | mod = provider.hosts.Length;
62 | }
63 |
64 | public Uri Get() => provider.hosts[random.Next(mod)];
65 | }
66 |
67 | private class RoundRobinSelector : IHostSelector
68 | {
69 | private readonly int mod;
70 | private readonly ProxmoxHostProvider provider;
71 | private int c;
72 |
73 | public RoundRobinSelector(ProxmoxHostProvider provider)
74 | {
75 | this.provider = provider;
76 | mod = provider.hosts.Length;
77 | c = mod;
78 | }
79 |
80 | public Uri Get() => provider.hosts[c = (c + 1) % mod];
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Pages/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Identity
2 | @inject SignInManager SignInManager
3 |
4 |
5 |
6 |
7 |
8 |
9 | @ViewData["Title"] - Proxmox Cloud
10 |
11 |
12 |
13 |
14 |
15 |
16 | This application requires JavaScript to be enabled in your web browser.
17 |
18 |
19 |
20 |
21 |
22 | Proxmox Cloud
23 |
24 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Project Name
37 |
38 |
40 |
41 |
42 | Project
43 |
65 |
66 | @if (SignInManager.IsSignedIn(User) && User.IsInRole("Administrator"))
67 | {
68 |
69 | Admin
70 |
77 |
78 | }
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | @RenderBody()
88 |
89 |
90 |
95 |
96 |
97 | @await RenderSectionAsync("Scripts", required: false)
98 |
99 |
--------------------------------------------------------------------------------
/proxmox-cloud/Data/ApplicationDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.ChangeTracking;
4 | using Microsoft.EntityFrameworkCore.Metadata;
5 | using System;
6 | using System.Runtime.InteropServices;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace proxmox_cloud.Data
11 | {
12 | [StructLayout(LayoutKind.Auto)]
13 | public class ApplicationDbContext : IdentityDbContext
14 | {
15 | public ApplicationDbContext(DbContextOptions options)
16 | : base(options)
17 | {
18 | }
19 |
20 | public virtual DbSet Flavors { get; set; }
21 |
22 | public virtual DbSet FlavorExtraSpecs { get; set; }
23 |
24 | public virtual DbSet FlavorProjects { get; set; }
25 |
26 | public virtual DbSet Images { get; set; }
27 |
28 | public virtual DbSet ImageLocations { get; set; }
29 |
30 | public virtual DbSet ImageProperties { get; set; }
31 |
32 | public virtual DbSet Instances { get; set; }
33 |
34 | public virtual DbSet Projects { get; set; }
35 |
36 | public virtual DbSet ProjectUsers { get; set; }
37 |
38 | public virtual DbSet Volumes { get; set; }
39 |
40 | public virtual DbSet VolumeAttachment { get; set; }
41 |
42 | protected override void OnModelCreating(ModelBuilder builder)
43 | {
44 | base.OnModelCreating(builder);
45 | builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
46 | }
47 |
48 | public override int SaveChanges(bool acceptAllChangesOnSuccess)
49 | {
50 | OnBeforeSaving();
51 | return base.SaveChanges(acceptAllChangesOnSuccess);
52 | }
53 | public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
54 | {
55 | OnBeforeSaving();
56 | return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
57 | }
58 |
59 | private void OnBeforeSaving()
60 | {
61 | var entries = ChangeTracker.Entries();
62 | var now = DateTime.UtcNow;
63 |
64 | foreach (var entry in entries)
65 | {
66 | foreach (var property in entry.Properties)
67 | {
68 | if (!(property.Metadata.ClrType == typeof(DateTime) || property.Metadata.ClrType == typeof(DateTime?)))
69 | {
70 | continue;
71 | }
72 |
73 | if ((property.Metadata.ValueGenerated & ValueGenerated.OnAdd) > 0 && entry.State == EntityState.Added)
74 | {
75 | property.CurrentValue = now;
76 | }
77 | if ((property.Metadata.ValueGenerated & (ValueGenerated.OnUpdate | ValueGenerated.OnUpdateSometimes)) > 0 && entry.State == EntityState.Modified)
78 | {
79 | property.CurrentValue = now;
80 | }
81 | }
82 | }
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Services/ProxmoxScraper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Hosting;
2 | using proxmox_cloud.ProxmoxApi;
3 | using proxmox_cloud.System.Threading;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Linq.Expressions;
8 | using System.Reflection;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 |
12 | namespace proxmox_cloud.Services
13 | {
14 | public class ProxmoxScraper : IHostedService
15 | {
16 | private readonly ReaderWriterLockSlim atomic = new();
17 | private readonly PveClientFactory clientFactory;
18 | private readonly Timer timer;
19 | private Scrape scrape;
20 |
21 | public ProxmoxScraper(PveClientFactory clientFactory)
22 | {
23 | timer = new(Handler);
24 | this.clientFactory = clientFactory;
25 | }
26 |
27 | public delegate void CollectAction(in CollectContext context);
28 |
29 | public void Collect(CollectAction action)
30 | {
31 | CollectContext context = new(this);
32 | using (atomic.UseReadLock())
33 | {
34 | action(context);
35 | }
36 | }
37 |
38 | public Task StartAsync(CancellationToken cancellationToken)
39 | {
40 | timer.Change(TimeSpan.Zero, TimeSpan.FromMilliseconds(500));
41 | return Task.CompletedTask;
42 | }
43 |
44 | public Task StopAsync(CancellationToken cancellationToken)
45 | {
46 | timer.Change(Timeout.Infinite, 0);
47 | return Task.CompletedTask;
48 | }
49 |
50 | private void Handler(object state)
51 | {
52 | var resources = clientFactory.Get().GetClusterResources().Result.Data;
53 | var types = resources.GroupBy(x => x.Type).ToDictionary(x => x.Key, x => (IEnumerable)x);
54 | IEnumerable hypervisors = Enumerable.Empty();
55 | if (types.TryGetValue(ClusterResourceType.Node, out var hypervisorResources))
56 | {
57 | hypervisors = hypervisorResources.Select(_ => new HypervisorModel(_)).ToList();
58 | }
59 | IEnumerable zones = Enumerable.Empty();
60 | if (types.TryGetValue(ClusterResourceType.SDN, out var sdnResources))
61 | {
62 | zones = sdnResources.Select(_ => new ZoneModel(_)).ToList();
63 | }
64 |
65 | using (atomic.UseWriteLock())
66 | {
67 | scrape = new()
68 | {
69 | Hypervisors = hypervisors,
70 | Zones = zones
71 | };
72 | }
73 | }
74 |
75 | public ref struct CollectContext
76 | {
77 | private readonly ProxmoxScraper scraper;
78 |
79 | public CollectContext(ProxmoxScraper scraper)
80 | {
81 | this.scraper = scraper;
82 | }
83 |
84 | public IEnumerable Hypervisors => Get(_ => _.Hypervisors);
85 |
86 | public IEnumerable Zones => Get(_ => _.Zones);
87 |
88 | private T Get(Expression> expr) => expr.Body is MemberExpression member &&
89 | member.Member is PropertyInfo property ? (T)property.GetValue(scraper.scrape) : default;
90 | }
91 |
92 | public struct Scrape
93 | {
94 | public IEnumerable Hypervisors { get; init; }
95 |
96 | public IEnumerable Zones { get; init; }
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Areas/Identity/Pages/Account/Login.cshtml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Linq;
5 | using System.Text.Encodings.Web;
6 | using System.Threading.Tasks;
7 | using Microsoft.AspNetCore.Authorization;
8 | using Microsoft.AspNetCore.Authentication;
9 | using Microsoft.AspNetCore.Identity;
10 | using Microsoft.AspNetCore.Identity.UI.Services;
11 | using Microsoft.AspNetCore.Mvc;
12 | using Microsoft.AspNetCore.Mvc.RazorPages;
13 | using Microsoft.Extensions.Logging;
14 |
15 | namespace proxmox_cloud.Areas.Identity.Pages.Account
16 | {
17 | [AllowAnonymous]
18 | public class LoginModel : PageModel
19 | {
20 | private readonly UserManager _userManager;
21 | private readonly SignInManager _signInManager;
22 | private readonly ILogger _logger;
23 |
24 | public LoginModel(SignInManager signInManager,
25 | ILogger logger,
26 | UserManager userManager)
27 | {
28 | _userManager = userManager;
29 | _signInManager = signInManager;
30 | _logger = logger;
31 | }
32 |
33 | [BindProperty]
34 | public InputModel Input { get; set; }
35 |
36 | public IList ExternalLogins { get; set; }
37 |
38 | public string ReturnUrl { get; set; }
39 |
40 | [TempData]
41 | public string ErrorMessage { get; set; }
42 |
43 | public class InputModel
44 | {
45 | [Required]
46 | public string Username { get; set; }
47 |
48 | [Required]
49 | [DataType(DataType.Password)]
50 | public string Password { get; set; }
51 |
52 | [Display(Name = "Remember me?")]
53 | public bool RememberMe { get; set; }
54 | }
55 |
56 | public async Task OnGetAsync(string returnUrl = null)
57 | {
58 | if (!string.IsNullOrEmpty(ErrorMessage))
59 | {
60 | ModelState.AddModelError(string.Empty, ErrorMessage);
61 | }
62 |
63 | returnUrl ??= Url.Content("~/");
64 |
65 | // Clear the existing external cookie to ensure a clean login process
66 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
67 |
68 | ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
69 |
70 | ReturnUrl = returnUrl;
71 | }
72 |
73 | public async Task OnPostAsync(string returnUrl = null)
74 | {
75 | returnUrl ??= Url.Content("~/");
76 |
77 | ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
78 |
79 | if (ModelState.IsValid)
80 | {
81 | // This doesn't count login failures towards account lockout
82 | // To enable password failures to trigger account lockout, set lockoutOnFailure: true
83 | var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, Input.RememberMe, lockoutOnFailure: false);
84 | if (result.Succeeded)
85 | {
86 | _logger.LogInformation("User logged in.");
87 | return LocalRedirect(returnUrl);
88 | }
89 | if (result.RequiresTwoFactor)
90 | {
91 | return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
92 | }
93 | if (result.IsLockedOut)
94 | {
95 | _logger.LogWarning("User account locked out.");
96 | return RedirectToPage("./Lockout");
97 | }
98 | else
99 | {
100 | ModelState.AddModelError(string.Empty, "Invalid login attempt.");
101 | return Page();
102 | }
103 | }
104 |
105 | // If we got this far, something failed, redisplay form
106 | return Page();
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/proxmox-cloud/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.AspNetCore.Hosting;
4 | using Microsoft.AspNetCore.Identity;
5 | using Microsoft.EntityFrameworkCore;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using Microsoft.Extensions.Hosting;
9 | using Polly;
10 | using proxmox_cloud.CephApi;
11 | using proxmox_cloud.Data;
12 | using proxmox_cloud.ProxmoxApi;
13 | using proxmox_cloud.Services;
14 | using Refit;
15 | using System;
16 | using System.Net;
17 | using System.Net.Http;
18 | using System.Threading;
19 | using System.Threading.Tasks;
20 |
21 | namespace proxmox_cloud
22 | {
23 | public class Startup
24 | {
25 | public Startup(IConfiguration configuration)
26 | {
27 | Configuration = configuration;
28 | }
29 |
30 | public IConfiguration Configuration { get; }
31 |
32 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
33 | {
34 | if (env.IsDevelopment())
35 | {
36 | app.UseDeveloperExceptionPage();
37 | app.UseMigrationsEndPoint();
38 | }
39 | else
40 | {
41 | app.UseExceptionHandler("/Home/Error");
42 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
43 | app.UseHsts();
44 | }
45 | //app.UseHttpsRedirection();
46 | app.UseStaticFiles();
47 |
48 | app.UseRouting();
49 |
50 | app.UseAuthentication();
51 | app.UseAuthorization();
52 |
53 | app.UseEndpoints(endpoints =>
54 | {
55 | endpoints.MapRazorPages().RequireAuthorization();
56 | endpoints.MapFallback(context =>
57 | {
58 | context.Response.Redirect("/Project");
59 | return Task.CompletedTask;
60 | });
61 | });
62 | }
63 |
64 | public void ConfigureServices(IServiceCollection services)
65 | {
66 | services.AddDbContextFactory(options =>
67 | options.UseSqlite(
68 | Configuration.GetConnectionString("DefaultConnection")));
69 | services.AddScoped(p => p.GetRequiredService>().CreateDbContext());
70 |
71 | services.AddSingleton();
72 | services.AddSingleton();
73 | services.AddSingleton();
74 |
75 | services.AddRefitClient(new RefitSettings()
76 | {
77 | ContentSerializer = new SystemTextJsonContentSerializer()
78 | }).ConfigureHttpClient(client =>
79 | {
80 | client.DefaultRequestHeaders.Add("User-Agent", "Proxmox-Cloud");
81 | }).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
82 | {
83 | ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
84 | }).AddHttpMessageHandler(s => ActivatorUtilities.CreateInstance(s));
85 |
86 | services.AddHttpClient(client =>
87 | {
88 | client.DefaultRequestHeaders.Add("User-Agent", "Proxmox-Cloud");
89 | }).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
90 | {
91 | ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
92 | }).AddHttpMessageHandler(s => ActivatorUtilities.CreateInstance(s))
93 | .AddPolicyHandler((provider, request) =>
94 | {
95 | return Policy.HandleResult(r => r.StatusCode == HttpStatusCode.Unauthorized)
96 | .RetryAsync(1, (response, retryCount, context) =>
97 | {
98 | var client = provider.GetRequiredService();
99 | });
100 | });
101 |
102 | services.AddDatabaseDeveloperPageExceptionFilter();
103 |
104 | services.AddAuthorization(options =>
105 | {
106 | options.AddPolicy("Admin", new AuthorizationPolicyBuilder()
107 | .RequireRole("Administrator")
108 | .Build());
109 | });
110 | services.AddDefaultIdentity()
111 | .AddRoles()
112 | .AddEntityFrameworkStores();
113 | services.AddRazorPages(options =>
114 | {
115 | options.Conventions.AuthorizeAreaFolder("Admin", "/", "Admin");
116 | });
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/proxmox-cloud/ProxmoxApi/IPveClient.ResponseTypes.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Runtime.Serialization;
5 | using System.Text.Json.Serialization;
6 |
7 | namespace proxmox_cloud.ProxmoxApi
8 | {
9 | public enum ClusterResourceType { Node, Storage, Pool, Qemu, LXC, OpenVZ, SDN };
10 |
11 | public enum ClusterStatusType { Cluster, Node }
12 |
13 | public enum NodeStatus { Unknown, Offline, Online }
14 |
15 | public enum SDNZoneType { EVPN, Faucet, QinQ, Simple, VLAN, VXLAN }
16 |
17 | public enum StorageType { Btrfs, CephFS, CIFS, Dir, GlusterFS, iSCSI, iSCSIDirect, LVM, LVMThin, NFS, PBS, RBD, ZFS, ZFSPool }
18 |
19 | public enum VLANProtocol
20 | {
21 | [EnumMember(Value = "802.1q")]
22 | IEEE8021q,
23 |
24 | [EnumMember(Value = "802.1ad")]
25 | IEEE8021ad
26 | }
27 |
28 | public enum VNetType
29 | {
30 | VNet
31 | }
32 |
33 | public static class ClusterResourceExtensions
34 | {
35 | public static object Map(this ClusterResource @this) => @this.Type switch
36 | {
37 | ClusterResourceType.Node => new HypervisorModel(@this),
38 | //TODO ClusterResourceType.LXC => new LXCModel(),
39 | //TODO ClusterResourceType.Pool => new PoolModel(),
40 | //TODO ClusterResourceType.Qemu => new VMModel(),
41 | ClusterResourceType.SDN => new ZoneModel(@this),
42 | //TODO ClusterResourceType.Storage => new StorageModel(),
43 | _ => throw new ArgumentException(@this.Type.ToString())
44 | };
45 | }
46 |
47 | public static class NodeResponseExtensions
48 | {
49 | public static HypervisorModel Map(this NodeResponse @this) => new(@this);
50 | }
51 |
52 | public sealed class ApplianceInfoResponse : JsonResponse { }
53 |
54 | public sealed class ArrayResponse
55 | {
56 | public Collection Data { get; init; }
57 | }
58 |
59 | public sealed class ClusterOptions : JsonResponse
60 | {
61 | }
62 |
63 | public sealed class ClusterResource : JsonResponse
64 | {
65 | public string Content { get; init; }
66 |
67 | public double CPU { get; init; }
68 |
69 | public long Disk { get; init; }
70 |
71 | public string HAState { get; init; }
72 |
73 | public string Id { get; init; }
74 |
75 | public double MaxCPU { get; init; }
76 |
77 | public long MaxDisk { get; init; }
78 |
79 | public long MaxMem { get; init; }
80 |
81 | public long Mem { get; init; }
82 |
83 | public string Name { get; init; }
84 |
85 | public string Node { get; init; }
86 |
87 | public string PluginType { get; init; }
88 |
89 | public string Pool { get; init; }
90 |
91 | public string Status { get; init; }
92 |
93 | public string Storage { get; init; }
94 |
95 | [JsonPropertyName("level")]
96 | public string SupportLevel { get; init; }
97 |
98 | public ClusterResourceType Type { get; init; }
99 |
100 | public int Uptime { get; init; }
101 | }
102 |
103 | public sealed class ClusterStatusResponse : JsonResponse
104 | {
105 | public string Id { get; init; }
106 |
107 | public string IP { get; init; }
108 |
109 | public bool Local { get; init; }
110 |
111 | public string Name { get; init; }
112 |
113 | public int NodeId { get; init; }
114 |
115 | public int Nodes { get; init; }
116 |
117 | public bool Online { get; init; }
118 |
119 | public bool Quorate { get; init; }
120 |
121 | [JsonPropertyName("level")]
122 | public string SupportLevel { get; init; }
123 |
124 | public ClusterStatusType Type { get; init; }
125 |
126 | public int Version { get; init; }
127 | }
128 |
129 | public abstract class JsonResponse
130 | {
131 | [JsonExtensionData]
132 | public Dictionary AdditionalProperties { get; init; }
133 | }
134 |
135 | public sealed class NodeConfigResponse : JsonResponse { }
136 |
137 | public sealed class NodeDNSResponse : JsonResponse { }
138 |
139 | public sealed class NodeResponse : JsonResponse
140 | {
141 | public double Cpu { get; init; }
142 |
143 | public long Disk { get; init; }
144 |
145 | public string Id { get; init; }
146 |
147 | public string Level { get; init; }
148 |
149 | public int MaxCpu { get; init; }
150 |
151 | public long MaxDisk { get; init; }
152 |
153 | public long MaxMem { get; init; }
154 |
155 | public long Mem { get; init; }
156 |
157 | public string Node { get; init; }
158 |
159 | [JsonPropertyName("ssl_fingerprint")]
160 | public string SSLFingerprint { get; init; }
161 |
162 | public NodeStatus Status { get; init; }
163 |
164 | public int UpTime { get; init; }
165 | }
166 |
167 | public sealed class ObjectResponse
168 | {
169 | public T Data { get; init; }
170 | }
171 |
172 | public sealed class PoolResponse : JsonResponse
173 | {
174 | public string Comment { get; init; }
175 |
176 | public string PoolId { get; init; }
177 | }
178 |
179 | public sealed class StorageResponse : JsonResponse
180 | {
181 | public string Content { get; init; }
182 |
183 | public string Digest { get; init; }
184 |
185 | [JsonIgnore]
186 | public bool Disable => DisableInt == 1;
187 |
188 | [JsonIgnore]
189 | public bool KRBD => KRBDInt == 1;
190 |
191 | public string Path { get; init; }
192 |
193 | public string Pool { get; init; }
194 |
195 | [JsonIgnore]
196 | public bool Shared => SharedInt == 1;
197 |
198 | public string Storage { get; init; }
199 |
200 | public StorageType Type { get; init; }
201 |
202 | [JsonPropertyName("disable")]
203 | private int DisableInt { get; init; }
204 |
205 | [JsonPropertyName("krbd")]
206 | private int KRBDInt { get; init; }
207 |
208 | [JsonPropertyName("shared")]
209 | private int SharedInt { get; init; }
210 | }
211 |
212 | public sealed class TicketResponse : JsonResponse
213 | {
214 | public string CSRFPreventionToken { get; init; }
215 |
216 | public string Ticket { get; init; }
217 | }
218 |
219 | public sealed class VersionResponse : JsonResponse
220 | {
221 | public string Release { get; init; }
222 |
223 | public string RepoId { get; init; }
224 |
225 | public string Version { get; init; }
226 | }
227 |
228 | public sealed class VirtualMachineResponse : JsonResponse
229 | {
230 | public double CPUs { get; init; }
231 |
232 | public string Lock { get; init; }
233 |
234 | [JsonPropertyName("running-machine")]
235 | public string MachineType { get; init; }
236 |
237 | public long MaxDisk { get; init; }
238 |
239 | public long MaxMem { get; init; }
240 |
241 | public string Name { get; init; }
242 |
243 | public int PID { get; init; }
244 |
245 | [JsonPropertyName("running-qemu")]
246 | public string QEMUVersion { get; init; }
247 |
248 | public string QMPStatus { get; init; }
249 |
250 | public string Tags { get; init; }
251 |
252 | public int Uptime { get; init; }
253 |
254 | public int VMId { get; init; }
255 | }
256 |
257 | public sealed class VNetResponse : JsonResponse
258 | {
259 | public string Alias { get; init; }
260 |
261 | public string Digest { get; init; }
262 |
263 | public int Tag { get; init; }
264 |
265 | public VNetType Type { get; init; }
266 |
267 | public string VNet { get; init; }
268 |
269 | public string Zone { get; init; }
270 | }
271 |
272 | public sealed class ZoneResponse : JsonResponse
273 | {
274 | public string Bridge { get; init; }
275 |
276 | public string Digest { get; init; }
277 |
278 | public string DNS { get; init; }
279 |
280 | public string DNSZone { get; init; }
281 |
282 | public string IPAM { get; init; }
283 |
284 | public int MTU { get; init; }
285 |
286 | public string Nodes { get; init; }
287 |
288 | public bool Pending { get; init; }
289 |
290 | public string ReverseDNS { get; init; }
291 |
292 | public string State { get; init; }
293 |
294 | public int Tag { get; init; }
295 |
296 | public SDNZoneType Type { get; init; }
297 |
298 | [JsonPropertyName("vlan-protocol")]
299 | public string VLANProtocol { get; init; }
300 |
301 | public string Zone { get; init; }
302 | }
303 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # Tye
66 | .tye/
67 |
68 | # ASP.NET Scaffolding
69 | ScaffoldingReadMe.txt
70 |
71 | # StyleCop
72 | StyleCopReport.xml
73 |
74 | # Files built by Visual Studio
75 | *_i.c
76 | *_p.c
77 | *_h.h
78 | *.ilk
79 | *.meta
80 | *.obj
81 | *.iobj
82 | *.pch
83 | *.pdb
84 | *.ipdb
85 | *.pgc
86 | *.pgd
87 | *.rsp
88 | *.sbr
89 | *.tlb
90 | *.tli
91 | *.tlh
92 | *.tmp
93 | *.tmp_proj
94 | *_wpftmp.csproj
95 | *.log
96 | *.vspscc
97 | *.vssscc
98 | .builds
99 | *.pidb
100 | *.svclog
101 | *.scc
102 |
103 | # Chutzpah Test files
104 | _Chutzpah*
105 |
106 | # Visual C++ cache files
107 | ipch/
108 | *.aps
109 | *.ncb
110 | *.opendb
111 | *.opensdf
112 | *.sdf
113 | *.cachefile
114 | *.VC.db
115 | *.VC.VC.opendb
116 |
117 | # Visual Studio profiler
118 | *.psess
119 | *.vsp
120 | *.vspx
121 | *.sap
122 |
123 | # Visual Studio Trace Files
124 | *.e2e
125 |
126 | # TFS 2012 Local Workspace
127 | $tf/
128 |
129 | # Guidance Automation Toolkit
130 | *.gpState
131 |
132 | # ReSharper is a .NET coding add-in
133 | _ReSharper*/
134 | *.[Rr]e[Ss]harper
135 | *.DotSettings.user
136 |
137 | # TeamCity is a build add-in
138 | _TeamCity*
139 |
140 | # DotCover is a Code Coverage Tool
141 | *.dotCover
142 |
143 | # AxoCover is a Code Coverage Tool
144 | .axoCover/*
145 | !.axoCover/settings.json
146 |
147 | # Coverlet is a free, cross platform Code Coverage Tool
148 | coverage*.json
149 | coverage*.xml
150 | coverage*.info
151 |
152 | # Visual Studio code coverage results
153 | *.coverage
154 | *.coveragexml
155 |
156 | # NCrunch
157 | _NCrunch_*
158 | .*crunch*.local.xml
159 | nCrunchTemp_*
160 |
161 | # MightyMoose
162 | *.mm.*
163 | AutoTest.Net/
164 |
165 | # Web workbench (sass)
166 | .sass-cache/
167 |
168 | # Installshield output folder
169 | [Ee]xpress/
170 |
171 | # DocProject is a documentation generator add-in
172 | DocProject/buildhelp/
173 | DocProject/Help/*.HxT
174 | DocProject/Help/*.HxC
175 | DocProject/Help/*.hhc
176 | DocProject/Help/*.hhk
177 | DocProject/Help/*.hhp
178 | DocProject/Help/Html2
179 | DocProject/Help/html
180 |
181 | # Click-Once directory
182 | publish/
183 |
184 | # Publish Web Output
185 | *.[Pp]ublish.xml
186 | *.azurePubxml
187 | # Note: Comment the next line if you want to checkin your web deploy settings,
188 | # but database connection strings (with potential passwords) will be unencrypted
189 | *.pubxml
190 | *.publishproj
191 |
192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
193 | # checkin your Azure Web App publish settings, but sensitive information contained
194 | # in these scripts will be unencrypted
195 | PublishScripts/
196 |
197 | # NuGet Packages
198 | *.nupkg
199 | # NuGet Symbol Packages
200 | *.snupkg
201 | # The packages folder can be ignored because of Package Restore
202 | **/[Pp]ackages/*
203 | # except build/, which is used as an MSBuild target.
204 | !**/[Pp]ackages/build/
205 | # Uncomment if necessary however generally it will be regenerated when needed
206 | #!**/[Pp]ackages/repositories.config
207 | # NuGet v3's project.json files produces more ignorable files
208 | *.nuget.props
209 | *.nuget.targets
210 |
211 | # Microsoft Azure Build Output
212 | csx/
213 | *.build.csdef
214 |
215 | # Microsoft Azure Emulator
216 | ecf/
217 | rcf/
218 |
219 | # Windows Store app package directories and files
220 | AppPackages/
221 | BundleArtifacts/
222 | Package.StoreAssociation.xml
223 | _pkginfo.txt
224 | *.appx
225 | *.appxbundle
226 | *.appxupload
227 |
228 | # Visual Studio cache files
229 | # files ending in .cache can be ignored
230 | *.[Cc]ache
231 | # but keep track of directories ending in .cache
232 | !?*.[Cc]ache/
233 |
234 | # Others
235 | ClientBin/
236 | ~$*
237 | *~
238 | *.dbmdl
239 | *.dbproj.schemaview
240 | *.jfm
241 | *.pfx
242 | *.publishsettings
243 | orleans.codegen.cs
244 |
245 | # Including strong name files can present a security risk
246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
247 | #*.snk
248 |
249 | # Since there are multiple workflows, uncomment next line to ignore bower_components
250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
251 | #bower_components/
252 |
253 | # RIA/Silverlight projects
254 | Generated_Code/
255 |
256 | # Backup & report files from converting an old project file
257 | # to a newer Visual Studio version. Backup files are not needed,
258 | # because we have git ;-)
259 | _UpgradeReport_Files/
260 | Backup*/
261 | UpgradeLog*.XML
262 | UpgradeLog*.htm
263 | ServiceFabricBackup/
264 | *.rptproj.bak
265 |
266 | # SQL Server files
267 | *.mdf
268 | *.ldf
269 | *.ndf
270 |
271 | # Business Intelligence projects
272 | *.rdl.data
273 | *.bim.layout
274 | *.bim_*.settings
275 | *.rptproj.rsuser
276 | *- [Bb]ackup.rdl
277 | *- [Bb]ackup ([0-9]).rdl
278 | *- [Bb]ackup ([0-9][0-9]).rdl
279 |
280 | # Microsoft Fakes
281 | FakesAssemblies/
282 |
283 | # GhostDoc plugin setting file
284 | *.GhostDoc.xml
285 |
286 | # Node.js Tools for Visual Studio
287 | .ntvs_analysis.dat
288 | node_modules/
289 |
290 | # Visual Studio 6 build log
291 | *.plg
292 |
293 | # Visual Studio 6 workspace options file
294 | *.opt
295 |
296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
297 | *.vbw
298 |
299 | # Visual Studio LightSwitch build output
300 | **/*.HTMLClient/GeneratedArtifacts
301 | **/*.DesktopClient/GeneratedArtifacts
302 | **/*.DesktopClient/ModelManifest.xml
303 | **/*.Server/GeneratedArtifacts
304 | **/*.Server/ModelManifest.xml
305 | _Pvt_Extensions
306 |
307 | # Paket dependency manager
308 | .paket/paket.exe
309 | paket-files/
310 |
311 | # FAKE - F# Make
312 | .fake/
313 |
314 | # CodeRush personal settings
315 | .cr/personal
316 |
317 | # Python Tools for Visual Studio (PTVS)
318 | __pycache__/
319 | *.pyc
320 |
321 | # Cake - Uncomment if you are using it
322 | # tools/**
323 | # !tools/packages.config
324 |
325 | # Tabs Studio
326 | *.tss
327 |
328 | # Telerik's JustMock configuration file
329 | *.jmconfig
330 |
331 | # BizTalk build output
332 | *.btp.cs
333 | *.btm.cs
334 | *.odx.cs
335 | *.xsd.cs
336 |
337 | # OpenCover UI analysis results
338 | OpenCover/
339 |
340 | # Azure Stream Analytics local run output
341 | ASALocalRun/
342 |
343 | # MSBuild Binary and Structured Log
344 | *.binlog
345 |
346 | # NVidia Nsight GPU debugger configuration file
347 | *.nvuser
348 |
349 | # MFractors (Xamarin productivity tool) working folder
350 | .mfractor/
351 |
352 | # Local History for Visual Studio
353 | .localhistory/
354 |
355 | # BeatPulse healthcheck temp database
356 | healthchecksdb
357 |
358 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
359 | MigrationBackup/
360 |
361 | # Ionide (cross platform F# VS Code tools) working folder
362 | .ionide/
363 |
364 | # Fody - auto-generated XML schema
365 | FodyWeavers.xsd
366 |
367 | ##
368 | ## Visual studio for Mac
369 | ##
370 |
371 |
372 | # globs
373 | Makefile.in
374 | *.userprefs
375 | *.usertasks
376 | config.make
377 | config.status
378 | aclocal.m4
379 | install-sh
380 | autom4te.cache/
381 | *.tar.gz
382 | tarballs/
383 | test-results/
384 |
385 | # Mac bundle stuff
386 | *.dmg
387 | *.app
388 |
389 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
390 | # General
391 | .DS_Store
392 | .AppleDouble
393 | .LSOverride
394 |
395 | # Icon must end with two \r
396 | Icon
397 |
398 |
399 | # Thumbnails
400 | ._*
401 |
402 | # Files that might appear in the root of a volume
403 | .DocumentRevisions-V100
404 | .fseventsd
405 | .Spotlight-V100
406 | .TemporaryItems
407 | .Trashes
408 | .VolumeIcon.icns
409 | .com.apple.timemachine.donotpresent
410 |
411 | # Directories potentially created on remote AFP share
412 | .AppleDB
413 | .AppleDesktop
414 | Network Trash Folder
415 | Temporary Items
416 | .apdisk
417 |
418 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
419 | # Windows thumbnail cache files
420 | Thumbs.db
421 | ehthumbs.db
422 | ehthumbs_vista.db
423 |
424 | # Dump file
425 | *.stackdump
426 |
427 | # Folder config file
428 | [Dd]esktop.ini
429 |
430 | # Recycle Bin used on file shares
431 | $RECYCLE.BIN/
432 |
433 | # Windows Installer files
434 | *.cab
435 | *.msi
436 | *.msix
437 | *.msm
438 | *.msp
439 |
440 | # Windows shortcuts
441 | *.lnk
442 |
443 | # JetBrains Rider
444 | .idea/
445 | *.sln.iml
446 |
447 | ##
448 | ## Visual Studio Code
449 | ##
450 | .vscode/*
451 | !.vscode/settings.json
452 | !.vscode/tasks.json
453 | !.vscode/launch.json
454 | !.vscode/extensions.json
455 |
--------------------------------------------------------------------------------
/proxmox-cloud/Data/Migrations/00000000000000_CreateIdentitySchema.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace proxmox_cloud.Data.Migrations
5 | {
6 | public partial class CreateIdentitySchema : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "AspNetRoles",
12 | columns: table => new
13 | {
14 | Id = table.Column(nullable: false),
15 | Name = table.Column(maxLength: 256, nullable: true),
16 | NormalizedName = table.Column(maxLength: 256, nullable: true),
17 | ConcurrencyStamp = table.Column(nullable: true)
18 | },
19 | constraints: table =>
20 | {
21 | table.PrimaryKey("PK_AspNetRoles", x => x.Id);
22 | });
23 |
24 | migrationBuilder.CreateTable(
25 | name: "AspNetUsers",
26 | columns: table => new
27 | {
28 | Id = table.Column(nullable: false),
29 | UserName = table.Column(maxLength: 256, nullable: true),
30 | NormalizedUserName = table.Column(maxLength: 256, nullable: true),
31 | Email = table.Column(maxLength: 256, nullable: true),
32 | NormalizedEmail = table.Column(maxLength: 256, nullable: true),
33 | EmailConfirmed = table.Column(nullable: false),
34 | PasswordHash = table.Column(nullable: true),
35 | SecurityStamp = table.Column(nullable: true),
36 | ConcurrencyStamp = table.Column(nullable: true),
37 | PhoneNumber = table.Column(nullable: true),
38 | PhoneNumberConfirmed = table.Column(nullable: false),
39 | TwoFactorEnabled = table.Column(nullable: false),
40 | LockoutEnd = table.Column(nullable: true),
41 | LockoutEnabled = table.Column(nullable: false),
42 | AccessFailedCount = table.Column(nullable: false)
43 | },
44 | constraints: table =>
45 | {
46 | table.PrimaryKey("PK_AspNetUsers", x => x.Id);
47 | });
48 |
49 | migrationBuilder.CreateTable(
50 | name: "AspNetRoleClaims",
51 | columns: table => new
52 | {
53 | Id = table.Column(nullable: false)
54 | .Annotation("Sqlite:Autoincrement", true),
55 | RoleId = table.Column(nullable: false),
56 | ClaimType = table.Column(nullable: true),
57 | ClaimValue = table.Column(nullable: true)
58 | },
59 | constraints: table =>
60 | {
61 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
62 | table.ForeignKey(
63 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
64 | column: x => x.RoleId,
65 | principalTable: "AspNetRoles",
66 | principalColumn: "Id",
67 | onDelete: ReferentialAction.Cascade);
68 | });
69 |
70 | migrationBuilder.CreateTable(
71 | name: "AspNetUserClaims",
72 | columns: table => new
73 | {
74 | Id = table.Column(nullable: false)
75 | .Annotation("Sqlite:Autoincrement", true),
76 | UserId = table.Column(nullable: false),
77 | ClaimType = table.Column(nullable: true),
78 | ClaimValue = table.Column(nullable: true)
79 | },
80 | constraints: table =>
81 | {
82 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
83 | table.ForeignKey(
84 | name: "FK_AspNetUserClaims_AspNetUsers_UserId",
85 | column: x => x.UserId,
86 | principalTable: "AspNetUsers",
87 | principalColumn: "Id",
88 | onDelete: ReferentialAction.Cascade);
89 | });
90 |
91 | migrationBuilder.CreateTable(
92 | name: "AspNetUserLogins",
93 | columns: table => new
94 | {
95 | LoginProvider = table.Column(maxLength: 128, nullable: false),
96 | ProviderKey = table.Column(maxLength: 128, nullable: false),
97 | ProviderDisplayName = table.Column(nullable: true),
98 | UserId = table.Column(nullable: false)
99 | },
100 | constraints: table =>
101 | {
102 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
103 | table.ForeignKey(
104 | name: "FK_AspNetUserLogins_AspNetUsers_UserId",
105 | column: x => x.UserId,
106 | principalTable: "AspNetUsers",
107 | principalColumn: "Id",
108 | onDelete: ReferentialAction.Cascade);
109 | });
110 |
111 | migrationBuilder.CreateTable(
112 | name: "AspNetUserRoles",
113 | columns: table => new
114 | {
115 | UserId = table.Column(nullable: false),
116 | RoleId = table.Column(nullable: false)
117 | },
118 | constraints: table =>
119 | {
120 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
121 | table.ForeignKey(
122 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
123 | column: x => x.RoleId,
124 | principalTable: "AspNetRoles",
125 | principalColumn: "Id",
126 | onDelete: ReferentialAction.Cascade);
127 | table.ForeignKey(
128 | name: "FK_AspNetUserRoles_AspNetUsers_UserId",
129 | column: x => x.UserId,
130 | principalTable: "AspNetUsers",
131 | principalColumn: "Id",
132 | onDelete: ReferentialAction.Cascade);
133 | });
134 |
135 | migrationBuilder.CreateTable(
136 | name: "AspNetUserTokens",
137 | columns: table => new
138 | {
139 | UserId = table.Column(nullable: false),
140 | LoginProvider = table.Column(maxLength: 128, nullable: false),
141 | Name = table.Column(maxLength: 128, nullable: false),
142 | Value = table.Column(nullable: true)
143 | },
144 | constraints: table =>
145 | {
146 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
147 | table.ForeignKey(
148 | name: "FK_AspNetUserTokens_AspNetUsers_UserId",
149 | column: x => x.UserId,
150 | principalTable: "AspNetUsers",
151 | principalColumn: "Id",
152 | onDelete: ReferentialAction.Cascade);
153 | });
154 |
155 | migrationBuilder.CreateIndex(
156 | name: "IX_AspNetRoleClaims_RoleId",
157 | table: "AspNetRoleClaims",
158 | column: "RoleId");
159 |
160 | migrationBuilder.CreateIndex(
161 | name: "RoleNameIndex",
162 | table: "AspNetRoles",
163 | column: "NormalizedName",
164 | unique: true);
165 |
166 | migrationBuilder.CreateIndex(
167 | name: "IX_AspNetUserClaims_UserId",
168 | table: "AspNetUserClaims",
169 | column: "UserId");
170 |
171 | migrationBuilder.CreateIndex(
172 | name: "IX_AspNetUserLogins_UserId",
173 | table: "AspNetUserLogins",
174 | column: "UserId");
175 |
176 | migrationBuilder.CreateIndex(
177 | name: "IX_AspNetUserRoles_RoleId",
178 | table: "AspNetUserRoles",
179 | column: "RoleId");
180 |
181 | migrationBuilder.CreateIndex(
182 | name: "EmailIndex",
183 | table: "AspNetUsers",
184 | column: "NormalizedEmail");
185 |
186 | migrationBuilder.CreateIndex(
187 | name: "UserNameIndex",
188 | table: "AspNetUsers",
189 | column: "NormalizedUserName",
190 | unique: true);
191 | }
192 |
193 | protected override void Down(MigrationBuilder migrationBuilder)
194 | {
195 | migrationBuilder.DropTable(
196 | name: "AspNetRoleClaims");
197 |
198 | migrationBuilder.DropTable(
199 | name: "AspNetUserClaims");
200 |
201 | migrationBuilder.DropTable(
202 | name: "AspNetUserLogins");
203 |
204 | migrationBuilder.DropTable(
205 | name: "AspNetUserRoles");
206 |
207 | migrationBuilder.DropTable(
208 | name: "AspNetUserTokens");
209 |
210 | migrationBuilder.DropTable(
211 | name: "AspNetRoles");
212 |
213 | migrationBuilder.DropTable(
214 | name: "AspNetUsers");
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/proxmox-cloud/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using proxmox_cloud.Data;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Migrations;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace proxmox_cloud.Data.Migrations
10 | {
11 | [DbContext(typeof(ApplicationDbContext))]
12 | [Migration("00000000000000_CreateIdentitySchema")]
13 | partial class CreateIdentitySchema
14 | {
15 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
16 | {
17 | #pragma warning disable 612, 618
18 | modelBuilder
19 | .HasAnnotation("ProductVersion", "3.0.0");
20 |
21 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
22 | {
23 | b.Property("Id")
24 | .HasColumnType("TEXT");
25 |
26 | b.Property("ConcurrencyStamp")
27 | .IsConcurrencyToken()
28 | .HasColumnType("TEXT");
29 |
30 | b.Property("Name")
31 | .HasColumnType("TEXT")
32 | .HasMaxLength(256);
33 |
34 | b.Property("NormalizedName")
35 | .HasColumnType("TEXT")
36 | .HasMaxLength(256);
37 |
38 | b.HasKey("Id");
39 |
40 | b.HasIndex("NormalizedName")
41 | .IsUnique()
42 | .HasName("RoleNameIndex");
43 |
44 | b.ToTable("AspNetRoles");
45 | });
46 |
47 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
48 | {
49 | b.Property("Id")
50 | .ValueGeneratedOnAdd()
51 | .HasColumnType("INTEGER");
52 |
53 | b.Property("ClaimType")
54 | .HasColumnType("TEXT");
55 |
56 | b.Property("ClaimValue")
57 | .HasColumnType("TEXT");
58 |
59 | b.Property("RoleId")
60 | .IsRequired()
61 | .HasColumnType("TEXT");
62 |
63 | b.HasKey("Id");
64 |
65 | b.HasIndex("RoleId");
66 |
67 | b.ToTable("AspNetRoleClaims");
68 | });
69 |
70 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
71 | {
72 | b.Property("Id")
73 | .HasColumnType("TEXT");
74 |
75 | b.Property("AccessFailedCount")
76 | .HasColumnType("INTEGER");
77 |
78 | b.Property("ConcurrencyStamp")
79 | .IsConcurrencyToken()
80 | .HasColumnType("TEXT");
81 |
82 | b.Property("Email")
83 | .HasColumnType("TEXT")
84 | .HasMaxLength(256);
85 |
86 | b.Property("EmailConfirmed")
87 | .HasColumnType("INTEGER");
88 |
89 | b.Property("LockoutEnabled")
90 | .HasColumnType("INTEGER");
91 |
92 | b.Property("LockoutEnd")
93 | .HasColumnType("TEXT");
94 |
95 | b.Property("NormalizedEmail")
96 | .HasColumnType("TEXT")
97 | .HasMaxLength(256);
98 |
99 | b.Property("NormalizedUserName")
100 | .HasColumnType("TEXT")
101 | .HasMaxLength(256);
102 |
103 | b.Property("PasswordHash")
104 | .HasColumnType("TEXT");
105 |
106 | b.Property("PhoneNumber")
107 | .HasColumnType("TEXT");
108 |
109 | b.Property("PhoneNumberConfirmed")
110 | .HasColumnType("INTEGER");
111 |
112 | b.Property("SecurityStamp")
113 | .HasColumnType("TEXT");
114 |
115 | b.Property("TwoFactorEnabled")
116 | .HasColumnType("INTEGER");
117 |
118 | b.Property("UserName")
119 | .HasColumnType("TEXT")
120 | .HasMaxLength(256);
121 |
122 | b.HasKey("Id");
123 |
124 | b.HasIndex("NormalizedEmail")
125 | .HasName("EmailIndex");
126 |
127 | b.HasIndex("NormalizedUserName")
128 | .IsUnique()
129 | .HasName("UserNameIndex");
130 |
131 | b.ToTable("AspNetUsers");
132 | });
133 |
134 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
135 | {
136 | b.Property("Id")
137 | .ValueGeneratedOnAdd()
138 | .HasColumnType("INTEGER");
139 |
140 | b.Property("ClaimType")
141 | .HasColumnType("TEXT");
142 |
143 | b.Property("ClaimValue")
144 | .HasColumnType("TEXT");
145 |
146 | b.Property("UserId")
147 | .IsRequired()
148 | .HasColumnType("TEXT");
149 |
150 | b.HasKey("Id");
151 |
152 | b.HasIndex("UserId");
153 |
154 | b.ToTable("AspNetUserClaims");
155 | });
156 |
157 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
158 | {
159 | b.Property("LoginProvider")
160 | .HasColumnType("TEXT")
161 | .HasMaxLength(128);
162 |
163 | b.Property("ProviderKey")
164 | .HasColumnType("TEXT")
165 | .HasMaxLength(128);
166 |
167 | b.Property("ProviderDisplayName")
168 | .HasColumnType("TEXT");
169 |
170 | b.Property("UserId")
171 | .IsRequired()
172 | .HasColumnType("TEXT");
173 |
174 | b.HasKey("LoginProvider", "ProviderKey");
175 |
176 | b.HasIndex("UserId");
177 |
178 | b.ToTable("AspNetUserLogins");
179 | });
180 |
181 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
182 | {
183 | b.Property("UserId")
184 | .HasColumnType("TEXT");
185 |
186 | b.Property("RoleId")
187 | .HasColumnType("TEXT");
188 |
189 | b.HasKey("UserId", "RoleId");
190 |
191 | b.HasIndex("RoleId");
192 |
193 | b.ToTable("AspNetUserRoles");
194 | });
195 |
196 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
197 | {
198 | b.Property("UserId")
199 | .HasColumnType("TEXT");
200 |
201 | b.Property("LoginProvider")
202 | .HasColumnType("TEXT")
203 | .HasMaxLength(128);
204 |
205 | b.Property("Name")
206 | .HasColumnType("TEXT")
207 | .HasMaxLength(128);
208 |
209 | b.Property("Value")
210 | .HasColumnType("TEXT");
211 |
212 | b.HasKey("UserId", "LoginProvider", "Name");
213 |
214 | b.ToTable("AspNetUserTokens");
215 | });
216 |
217 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
218 | {
219 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
220 | .WithMany()
221 | .HasForeignKey("RoleId")
222 | .OnDelete(DeleteBehavior.Cascade)
223 | .IsRequired();
224 | });
225 |
226 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
227 | {
228 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
229 | .WithMany()
230 | .HasForeignKey("UserId")
231 | .OnDelete(DeleteBehavior.Cascade)
232 | .IsRequired();
233 | });
234 |
235 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
236 | {
237 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
238 | .WithMany()
239 | .HasForeignKey("UserId")
240 | .OnDelete(DeleteBehavior.Cascade)
241 | .IsRequired();
242 | });
243 |
244 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
245 | {
246 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
247 | .WithMany()
248 | .HasForeignKey("RoleId")
249 | .OnDelete(DeleteBehavior.Cascade)
250 | .IsRequired();
251 |
252 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
253 | .WithMany()
254 | .HasForeignKey("UserId")
255 | .OnDelete(DeleteBehavior.Cascade)
256 | .IsRequired();
257 | });
258 |
259 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
260 | {
261 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
262 | .WithMany()
263 | .HasForeignKey("UserId")
264 | .OnDelete(DeleteBehavior.Cascade)
265 | .IsRequired();
266 | });
267 | #pragma warning restore 612, 618
268 | }
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | EUROPEAN UNION PUBLIC LICENCE v. 1.2
2 | EUPL © the European Union 2007, 2016
3 |
4 | This European Union Public Licence (the ‘EUPL’) applies to the Work (as
5 | defined below) which is provided under the terms of this Licence. Any use of
6 | the Work, other than as authorised under this Licence is prohibited (to the
7 | extent such use is covered by a right of the copyright holder of the Work).
8 |
9 | The Work is provided under the terms of this Licence when the Licensor (as
10 | defined below) has placed the following notice immediately following the
11 | copyright notice for the Work:
12 |
13 | Licensed under the EUPL
14 |
15 | or has expressed by any other means his willingness to license under the EUPL.
16 |
17 | 1. Definitions
18 |
19 | In this Licence, the following terms have the following meaning:
20 |
21 | - ‘The Licence’: this Licence.
22 |
23 | - ‘The Original Work’: the work or software distributed or communicated by the
24 | Licensor under this Licence, available as Source Code and also as Executable
25 | Code as the case may be.
26 |
27 | - ‘Derivative Works’: the works or software that could be created by the
28 | Licensee, based upon the Original Work or modifications thereof. This
29 | Licence does not define the extent of modification or dependence on the
30 | Original Work required in order to classify a work as a Derivative Work;
31 | this extent is determined by copyright law applicable in the country
32 | mentioned in Article 15.
33 |
34 | - ‘The Work’: the Original Work or its Derivative Works.
35 |
36 | - ‘The Source Code’: the human-readable form of the Work which is the most
37 | convenient for people to study and modify.
38 |
39 | - ‘The Executable Code’: any code which has generally been compiled and which
40 | is meant to be interpreted by a computer as a program.
41 |
42 | - ‘The Licensor’: the natural or legal person that distributes or communicates
43 | the Work under the Licence.
44 |
45 | - ‘Contributor(s)’: any natural or legal person who modifies the Work under
46 | the Licence, or otherwise contributes to the creation of a Derivative Work.
47 |
48 | - ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
49 | the Work under the terms of the Licence.
50 |
51 | - ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
52 | renting, distributing, communicating, transmitting, or otherwise making
53 | available, online or offline, copies of the Work or providing access to its
54 | essential functionalities at the disposal of any other natural or legal
55 | person.
56 |
57 | 2. Scope of the rights granted by the Licence
58 |
59 | The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
60 | sublicensable licence to do the following, for the duration of copyright
61 | vested in the Original Work:
62 |
63 | - use the Work in any circumstance and for all usage,
64 | - reproduce the Work,
65 | - modify the Work, and make Derivative Works based upon the Work,
66 | - communicate to the public, including the right to make available or display
67 | the Work or copies thereof to the public and perform publicly, as the case
68 | may be, the Work,
69 | - distribute the Work or copies thereof,
70 | - lend and rent the Work or copies thereof,
71 | - sublicense rights in the Work or copies thereof.
72 |
73 | Those rights can be exercised on any media, supports and formats, whether now
74 | known or later invented, as far as the applicable law permits so.
75 |
76 | In the countries where moral rights apply, the Licensor waives his right to
77 | exercise his moral right to the extent allowed by law in order to make
78 | effective the licence of the economic rights here above listed.
79 |
80 | The Licensor grants to the Licensee royalty-free, non-exclusive usage rights
81 | to any patents held by the Licensor, to the extent necessary to make use of
82 | the rights granted on the Work under this Licence.
83 |
84 | 3. Communication of the Source Code
85 |
86 | The Licensor may provide the Work either in its Source Code form, or as
87 | Executable Code. If the Work is provided as Executable Code, the Licensor
88 | provides in addition a machine-readable copy of the Source Code of the Work
89 | along with each copy of the Work that the Licensor distributes or indicates,
90 | in a notice following the copyright notice attached to the Work, a repository
91 | where the Source Code is easily and freely accessible for as long as the
92 | Licensor continues to distribute or communicate the Work.
93 |
94 | 4. Limitations on copyright
95 |
96 | Nothing in this Licence is intended to deprive the Licensee of the benefits
97 | from any exception or limitation to the exclusive rights of the rights owners
98 | in the Work, of the exhaustion of those rights or of other applicable
99 | limitations thereto.
100 |
101 | 5. Obligations of the Licensee
102 |
103 | The grant of the rights mentioned above is subject to some restrictions and
104 | obligations imposed on the Licensee. Those obligations are the following:
105 |
106 | Attribution right: The Licensee shall keep intact all copyright, patent or
107 | trademarks notices and all notices that refer to the Licence and to the
108 | disclaimer of warranties. The Licensee must include a copy of such notices and
109 | a copy of the Licence with every copy of the Work he/she distributes or
110 | communicates. The Licensee must cause any Derivative Work to carry prominent
111 | notices stating that the Work has been modified and the date of modification.
112 |
113 | Copyleft clause: If the Licensee distributes or communicates copies of the
114 | Original Works or Derivative Works, this Distribution or Communication will be
115 | done under the terms of this Licence or of a later version of this Licence
116 | unless the Original Work is expressly distributed only under this version of
117 | the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
118 | (becoming Licensor) cannot offer or impose any additional terms or conditions
119 | on the Work or Derivative Work that alter or restrict the terms of the
120 | Licence.
121 |
122 | Compatibility clause: If the Licensee Distributes or Communicates Derivative
123 | Works or copies thereof based upon both the Work and another work licensed
124 | under a Compatible Licence, this Distribution or Communication can be done
125 | under the terms of this Compatible Licence. For the sake of this clause,
126 | ‘Compatible Licence’ refers to the licences listed in the appendix attached to
127 | this Licence. Should the Licensee's obligations under the Compatible Licence
128 | conflict with his/her obligations under this Licence, the obligations of the
129 | Compatible Licence shall prevail.
130 |
131 | Provision of Source Code: When distributing or communicating copies of the
132 | Work, the Licensee will provide a machine-readable copy of the Source Code or
133 | indicate a repository where this Source will be easily and freely available
134 | for as long as the Licensee continues to distribute or communicate the Work.
135 |
136 | Legal Protection: This Licence does not grant permission to use the trade
137 | names, trademarks, service marks, or names of the Licensor, except as required
138 | for reasonable and customary use in describing the origin of the Work and
139 | reproducing the content of the copyright notice.
140 |
141 | 6. Chain of Authorship
142 |
143 | The original Licensor warrants that the copyright in the Original Work granted
144 | hereunder is owned by him/her or licensed to him/her and that he/she has the
145 | power and authority to grant the Licence.
146 |
147 | Each Contributor warrants that the copyright in the modifications he/she
148 | brings to the Work are owned by him/her or licensed to him/her and that he/she
149 | has the power and authority to grant the Licence.
150 |
151 | Each time You accept the Licence, the original Licensor and subsequent
152 | Contributors grant You a licence to their contributions to the Work, under the
153 | terms of this Licence.
154 |
155 | 7. Disclaimer of Warranty
156 |
157 | The Work is a work in progress, which is continuously improved by numerous
158 | Contributors. It is not a finished work and may therefore contain defects or
159 | ‘bugs’ inherent to this type of development.
160 |
161 | For the above reason, the Work is provided under the Licence on an ‘as is’
162 | basis and without warranties of any kind concerning the Work, including
163 | without limitation merchantability, fitness for a particular purpose, absence
164 | of defects or errors, accuracy, non-infringement of intellectual property
165 | rights other than copyright as stated in Article 6 of this Licence.
166 |
167 | This disclaimer of warranty is an essential part of the Licence and a
168 | condition for the grant of any rights to the Work.
169 |
170 | 8. Disclaimer of Liability
171 |
172 | Except in the cases of wilful misconduct or damages directly caused to natural
173 | persons, the Licensor will in no event be liable for any direct or indirect,
174 | material or moral, damages of any kind, arising out of the Licence or of the
175 | use of the Work, including without limitation, damages for loss of goodwill,
176 | work stoppage, computer failure or malfunction, loss of data or any commercial
177 | damage, even if the Licensor has been advised of the possibility of such
178 | damage. However, the Licensor will be liable under statutory product liability
179 | laws as far such laws apply to the Work.
180 |
181 | 9. Additional agreements
182 |
183 | While distributing the Work, You may choose to conclude an additional
184 | agreement, defining obligations or services consistent with this Licence.
185 | However, if accepting obligations, You may act only on your own behalf and on
186 | your sole responsibility, not on behalf of the original Licensor or any other
187 | Contributor, and only if You agree to indemnify, defend, and hold each
188 | Contributor harmless for any liability incurred by, or claims asserted against
189 | such Contributor by the fact You have accepted any warranty or additional
190 | liability.
191 |
192 | 10. Acceptance of the Licence
193 |
194 | The provisions of this Licence can be accepted by clicking on an icon ‘I
195 | agree’ placed under the bottom of a window displaying the text of this Licence
196 | or by affirming consent in any other similar way, in accordance with the rules
197 | of applicable law. Clicking on that icon indicates your clear and irrevocable
198 | acceptance of this Licence and all of its terms and conditions.
199 |
200 | Similarly, you irrevocably accept this Licence and all of its terms and
201 | conditions by exercising any rights granted to You by Article 2 of this
202 | Licence, such as the use of the Work, the creation by You of a Derivative Work
203 | or the Distribution or Communication by You of the Work or copies thereof.
204 |
205 | 11. Information to the public
206 |
207 | In case of any Distribution or Communication of the Work by means of
208 | electronic communication by You (for example, by offering to download the Work
209 | from a remote location) the distribution channel or media (for example, a
210 | website) must at least provide to the public the information requested by the
211 | applicable law regarding the Licensor, the Licence and the way it may be
212 | accessible, concluded, stored and reproduced by the Licensee.
213 |
214 | 12. Termination of the Licence
215 |
216 | The Licence and the rights granted hereunder will terminate automatically upon
217 | any breach by the Licensee of the terms of the Licence.
218 |
219 | Such a termination will not terminate the licences of any person who has
220 | received the Work from the Licensee under the Licence, provided such persons
221 | remain in full compliance with the Licence.
222 |
223 | 13. Miscellaneous
224 |
225 | Without prejudice of Article 9 above, the Licence represents the complete
226 | agreement between the Parties as to the Work.
227 |
228 | If any provision of the Licence is invalid or unenforceable under applicable
229 | law, this will not affect the validity or enforceability of the Licence as a
230 | whole. Such provision will be construed or reformed so as necessary to make it
231 | valid and enforceable.
232 |
233 | The European Commission may publish other linguistic versions or new versions
234 | of this Licence or updated versions of the Appendix, so far this is required
235 | and reasonable, without reducing the scope of the rights granted by the
236 | Licence. New versions of the Licence will be published with a unique version
237 | number.
238 |
239 | All linguistic versions of this Licence, approved by the European Commission,
240 | have identical value. Parties can take advantage of the linguistic version of
241 | their choice.
242 |
243 | 14. Jurisdiction
244 |
245 | Without prejudice to specific agreement between parties,
246 |
247 | - any litigation resulting from the interpretation of this License, arising
248 | between the European Union institutions, bodies, offices or agencies, as a
249 | Licensor, and any Licensee, will be subject to the jurisdiction of the Court
250 | of Justice of the European Union, as laid down in article 272 of the Treaty
251 | on the Functioning of the European Union,
252 |
253 | - any litigation arising between other parties and resulting from the
254 | interpretation of this License, will be subject to the exclusive
255 | jurisdiction of the competent court where the Licensor resides or conducts
256 | its primary business.
257 |
258 | 15. Applicable Law
259 |
260 | Without prejudice to specific agreement between parties,
261 |
262 | - this Licence shall be governed by the law of the European Union Member State
263 | where the Licensor has his seat, resides or has his registered office,
264 |
265 | - this licence shall be governed by Belgian law if the Licensor has no seat,
266 | residence or registered office inside a European Union Member State.
267 |
268 | Appendix
269 |
270 | ‘Compatible Licences’ according to Article 5 EUPL are:
271 |
272 | - GNU General Public License (GPL) v. 2, v. 3
273 | - GNU Affero General Public License (AGPL) v. 3
274 | - Open Software License (OSL) v. 2.1, v. 3.0
275 | - Eclipse Public License (EPL) v. 1.0
276 | - CeCILL v. 2.0, v. 2.1
277 | - Mozilla Public Licence (MPL) v. 2
278 | - GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
279 | - Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
280 | works other than software
281 | - European Union Public Licence (EUPL) v. 1.1, v. 1.2
282 | - Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
283 | Reciprocity (LiLiQ-R+).
284 |
285 | The European Commission may update this Appendix to later versions of the
286 | above licences without producing a new version of the EUPL, as long as they
287 | provide the rights granted in Article 2 of this Licence and protect the
288 | covered Source Code from exclusive appropriation.
289 |
290 | All other changes or additions to this Appendix require the production of a
291 | new EUPL version.
--------------------------------------------------------------------------------
/proxmox-cloud/Data/Models.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.Metadata;
4 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
5 | using proxmox_cloud.Microsoft.EntityFrameworkCore;
6 | using System;
7 | using System.ComponentModel.DataAnnotations;
8 | using System.ComponentModel.DataAnnotations.Schema;
9 | using System.Runtime.InteropServices;
10 | using static Microsoft.EntityFrameworkCore.DeleteBehavior;
11 | using static System.Runtime.InteropServices.LayoutKind;
12 |
13 | // sources:
14 | // * TimestampMixin / SoftDeleteMixin: https://opendev.org/openstack/oslo.db/src/branch/master/oslo_db/sqlalchemy/models.py
15 | // * Identity: https://opendev.org/openstack/keystone/src/branch/master/keystone/identity/backends/sql_model.py
16 | // * Images: https://opendev.org/openstack/glance/src/branch/master/glance/db/sqlalchemy/models.py
17 | // * Volumes: https://opendev.org/openstack/cinder/src/branch/master/cinder/db/sqlalchemy/models.py
18 | // * Instances: https://opendev.org/openstack/nova/src/branch/master/nova/db/main/models.py
19 | // * Instances 2: https://opendev.org/openstack/nova/src/branch/master/nova/db/api/models.py
20 | namespace proxmox_cloud.Data
21 | {
22 | public abstract class ConfiguredType where T : ConfiguredType
23 | {
24 | public abstract class TypeConfiguration : IEntityTypeConfiguration
25 | {
26 | public abstract void Configure(EntityTypeBuilder builder);
27 | }
28 | }
29 |
30 | [StructLayout(Auto)]
31 | public class Flavor : ConfiguredType
32 | {
33 | public virtual string Id { get; set; } = Guid.NewGuid().ToString();
34 |
35 | public virtual string Name { get; set; }
36 |
37 | public virtual int MemoryMb { get; set; }
38 |
39 | public virtual int vCPUs { get; set; }
40 |
41 | public virtual bool Disabled { get; set; } = false;
42 |
43 | public virtual bool IsPublic { get; set; }
44 |
45 | public virtual string Description { get; set; }
46 |
47 | public virtual DateTime CreatedAt { get; }
48 |
49 | public virtual DateTime UpdatedAt { get; }
50 |
51 | public sealed class Configuration : TypeConfiguration
52 | {
53 | public override void Configure(EntityTypeBuilder builder)
54 | {
55 | builder.Property(x => x.Name).IsRequired();
56 |
57 | builder.Property(x => x.CreatedAt)
58 | .ValueGeneration(ValueGenerated.OnAdd);
59 | builder.Property(x => x.UpdatedAt)
60 | .ValueGeneration(ValueGenerated.OnAddOrUpdate);
61 |
62 | builder.HasIndex(x => x.Name).IsUnique();
63 | }
64 | }
65 | }
66 |
67 | [StructLayout(Auto)]
68 | public class FlavorExtraSpec : ConfiguredType
69 | {
70 | public virtual Flavor Flavor { get; set; }
71 |
72 | public virtual string Key { get; set; }
73 |
74 | public virtual string Value { get; set; }
75 |
76 | public virtual DateTime CreatedAt { get; }
77 |
78 | public virtual DateTime UpdatedAt { get; }
79 |
80 | public virtual DateTime? DeletedAt { get; set; }
81 |
82 | public sealed class Configuration : TypeConfiguration
83 | {
84 | public override void Configure(EntityTypeBuilder builder)
85 | {
86 | builder.HasNoKey();
87 |
88 | builder.Property(x => x.Key).IsRequired();
89 | builder.Property(x => x.CreatedAt)
90 | .ValueGeneration(ValueGenerated.OnAdd);
91 | builder.Property(x => x.UpdatedAt)
92 | .ValueGeneration(ValueGenerated.OnAddOrUpdate);
93 |
94 | builder.HasOne(x => x.Flavor).WithMany().HasForeignKey("FlavorId")
95 | .IsRequired()
96 | .OnDelete(Cascade);
97 |
98 | builder.HasIndex("Key", "FlavorId").IsUnique();
99 | }
100 | }
101 | }
102 |
103 | [StructLayout(Auto)]
104 | public class FlavorProject : ConfiguredType
105 | {
106 | public Flavor Flavor { get; set; }
107 |
108 | public Project Project { get; set; }
109 |
110 | public sealed class Configuration : TypeConfiguration
111 | {
112 | public override void Configure(EntityTypeBuilder builder)
113 | {
114 | builder.HasNoKey();
115 |
116 | builder.HasOne(x => x.Flavor).WithMany().HasForeignKey("FlavorId")
117 | .IsRequired()
118 | .OnDelete(Cascade);
119 | builder.HasOne(x => x.Project).WithMany().HasForeignKey("ProjectId")
120 | .IsRequired()
121 | .OnDelete(Cascade);
122 |
123 | builder.HasIndex("FlavorId", "ProjectId").IsUnique();
124 | }
125 | }
126 | }
127 |
128 | [StructLayout(Auto)]
129 | public class Image : ConfiguredType
130 | {
131 | public virtual string Id { get; set; } = Guid.NewGuid().ToString();
132 |
133 | public virtual string Name { get; set; }
134 |
135 | // disk_format
136 | // container_format
137 | public virtual long Size { get; set; }
138 |
139 | public virtual long VirtualSize { get; set; }
140 |
141 | public virtual Visibilities Visibility { get; set; } = Visibilities.shared;
142 |
143 | // checksum
144 | // os_hash_algo
145 |
146 | public virtual int MinDisk { get; set; }
147 |
148 | public virtual int MinRam { get; set; }
149 |
150 | public virtual IdentityUser Owner { get; set; }
151 |
152 | public virtual bool Protected { get; set; }
153 |
154 | public virtual DateTime CreatedAt { get; }
155 |
156 | public virtual DateTime UpdatedAt { get; }
157 |
158 | public virtual DateTime? DeletedAt { get; set; }
159 |
160 | public sealed class Configuration : TypeConfiguration
161 | {
162 | public override void Configure(EntityTypeBuilder builder)
163 | {
164 | builder.Property(x => x.CreatedAt)
165 | .ValueGeneration(ValueGenerated.OnAdd);
166 | builder.Property(x => x.UpdatedAt)
167 | .ValueGeneration(ValueGenerated.OnAddOrUpdate);
168 |
169 | builder.HasOne(x => x.Owner)
170 | .WithMany()
171 | .HasForeignKey("OwnerId")
172 | .OnDelete(Restrict)
173 | .IsRequired();
174 |
175 | builder.HasIndex(x => x.Name)
176 | .IsUnique();
177 | }
178 | }
179 |
180 | public enum Visibilities
181 | {
182 | @private,
183 | @public,
184 | shared,
185 | community
186 | }
187 | }
188 |
189 | [StructLayout(Auto)]
190 | public class ImageLocation : ConfiguredType
191 | {
192 | public virtual Image Image { get; set; }
193 |
194 | public virtual string Value { get; set; }
195 |
196 | public sealed class Configuration : TypeConfiguration
197 | {
198 | public override void Configure(EntityTypeBuilder builder)
199 | {
200 | builder.HasNoKey();
201 |
202 | builder.HasOne(x => x.Image).WithOne()
203 | .HasForeignKey("ImageId")
204 | .OnDelete(Restrict)
205 | .IsRequired();
206 | }
207 | }
208 | }
209 |
210 | [StructLayout(Auto)]
211 | public class ImageProperty : ConfiguredType
212 | {
213 | public virtual Image Image { get; set; }
214 |
215 | public virtual string Name { get; set; }
216 |
217 | public virtual string Value { get; set; }
218 |
219 | public sealed class Configuration : TypeConfiguration
220 | {
221 | public override void Configure(EntityTypeBuilder builder)
222 | {
223 | builder.HasNoKey();
224 |
225 | builder.HasOne(x => x.Image).WithMany()
226 | .HasForeignKey("ImageId")
227 | .OnDelete(Restrict)
228 | .IsRequired();
229 |
230 | builder.HasIndex("ImageId", "Name").IsUnique();
231 | }
232 | }
233 | }
234 |
235 | [StructLayout(Auto)]
236 | public class Instance : ConfiguredType
237 | {
238 | public virtual string Id { get; set; } = Guid.NewGuid().ToString();
239 |
240 | public virtual IdentityUser User { get; set; }
241 |
242 | public virtual Project Project { get; set; }
243 |
244 | public virtual Image Image { get; set; }
245 |
246 | public virtual int MemoryMb { get; set; }
247 |
248 | public virtual int VCPU { get; set; }
249 |
250 | public virtual int RootGB { get; set; }
251 |
252 | public virtual Flavor Flavor { get; set; }
253 |
254 | public virtual DateTime LaunchedAt { get; set; }
255 |
256 | public virtual DateTime TerminatedAt { get; set; }
257 |
258 | public virtual string DisplayName { get; set; }
259 |
260 | public virtual string DisplayDescription { get; set; }
261 |
262 | public virtual DateTime CreatedAt { get; }
263 |
264 | public virtual DateTime UpdatedAt { get; }
265 |
266 | public virtual DateTime? DeletedAt { get; set; }
267 |
268 | public sealed class Configuration : TypeConfiguration
269 | {
270 | public override void Configure(EntityTypeBuilder builder)
271 | {
272 | builder.Property(x => x.CreatedAt)
273 | .ValueGeneration(ValueGenerated.OnAdd);
274 | builder.Property(x => x.UpdatedAt)
275 | .ValueGeneration(ValueGenerated.OnAddOrUpdate);
276 |
277 | builder.HasOne(x => x.User).WithMany()
278 | .HasForeignKey("UserId")
279 | .OnDelete(Restrict)
280 | .IsRequired();
281 | builder.HasOne(x => x.Project).WithMany()
282 | .HasForeignKey("ProjectId")
283 | .OnDelete(Restrict)
284 | .IsRequired();
285 | builder.HasOne(x => x.Image).WithMany()
286 | .HasForeignKey("ImageId")
287 | .OnDelete(Restrict)
288 | .IsRequired();
289 | builder.HasOne(x => x.Flavor).WithMany()
290 | .HasForeignKey("FlavorId")
291 | .OnDelete(Restrict)
292 | .IsRequired();
293 | }
294 | }
295 | }
296 |
297 | public partial class Network
298 | {
299 | public virtual DateTime CreatedAt { get; }
300 |
301 | public virtual DateTime? DeletedAt { get; set; }
302 |
303 | public virtual string Id { get; set; } = Guid.NewGuid().ToString();
304 |
305 | [ForeignKey("Id"), Required]
306 | public virtual IdentityUser Owner { get; set; }
307 |
308 | [ForeignKey("Id"), Required]
309 | public virtual Project Project { get; set; }
310 |
311 | public virtual DateTime UpdatedAt { get; }
312 | }
313 |
314 | [StructLayout(Auto)]
315 | public class Project : ConfiguredType
316 | {
317 | public virtual string Id { get; set; } = Guid.NewGuid().ToString();
318 |
319 | public virtual string Name { get; set; }
320 |
321 | public virtual string Description { get; set; }
322 |
323 | public virtual bool Enabled { get; set; }
324 |
325 | public virtual Project Parent { get; set; }
326 |
327 | public sealed class Configuration : TypeConfiguration
328 | {
329 | public override void Configure(EntityTypeBuilder builder)
330 | {
331 | builder.HasOne(x => x.Parent).WithMany()
332 | .HasForeignKey("ParentId")
333 | .OnDelete(Restrict);
334 |
335 | builder.HasIndex(x => x.Name).IsUnique();
336 | }
337 | }
338 | }
339 |
340 | [StructLayout(Auto)]
341 | public class ProjectUser : ConfiguredType
342 | {
343 | public virtual Project Project { get; set; }
344 |
345 | public virtual IdentityUser User { get; set; }
346 |
347 | public sealed class Configuration : TypeConfiguration
348 | {
349 | public override void Configure(EntityTypeBuilder builder)
350 | {
351 | builder.HasNoKey();
352 |
353 | builder.HasOne(x => x.Project).WithMany()
354 | .HasForeignKey("ParentId")
355 | .OnDelete(Restrict)
356 | .IsRequired();
357 | builder.HasOne(x => x.User).WithMany()
358 | .HasForeignKey("UserId")
359 | .OnDelete(Restrict)
360 | .IsRequired();
361 |
362 | builder.HasIndex("ParentId", "UserId").IsUnique();
363 | }
364 | }
365 | }
366 |
367 | [AttributeUsage(AttributeTargets.Property)]
368 | public sealed class TimestampAttribute : Attribute { }
369 |
370 | [StructLayout(Auto)]
371 | public class Volume : ConfiguredType
372 | {
373 | public virtual string Id { get; set; } = Guid.NewGuid().ToString();
374 |
375 | public virtual IdentityUser User { get; set; }
376 |
377 | public virtual Project Project { get; set; }
378 |
379 | public virtual int Size { get; set; }
380 |
381 | public virtual DateTime ScheduledAt { get; set; }
382 |
383 | public virtual DateTime LaunchedAt { get; set; }
384 |
385 | public virtual DateTime TerminatedAt { get; set; }
386 |
387 | public virtual string DisplayName { get; set; }
388 |
389 | public virtual string DisplayDescription { get; set; }
390 |
391 | public sealed class Configuration : TypeConfiguration
392 | {
393 | public override void Configure(EntityTypeBuilder builder)
394 | {
395 | builder.HasOne(x => x.User).WithMany()
396 | .HasForeignKey("UserId")
397 | .OnDelete(Restrict)
398 | .IsRequired();
399 | builder.HasOne(x => x.Project).WithMany()
400 | .HasForeignKey("ProjctId")
401 | .OnDelete(Restrict)
402 | .IsRequired();
403 | }
404 | }
405 | }
406 |
407 | [StructLayout(Auto)]
408 | public class VolumeAttachment : ConfiguredType
409 | {
410 | public virtual Volume Volume { get; set; }
411 |
412 | public virtual Instance Instance { get; set; }
413 |
414 | public sealed class Configuration : TypeConfiguration
415 | {
416 | public override void Configure(EntityTypeBuilder builder)
417 | {
418 | builder.HasNoKey();
419 |
420 | builder.HasOne(x => x.Volume).WithMany()
421 | .HasForeignKey("VolumeId")
422 | .OnDelete(Restrict)
423 | .IsRequired();
424 | builder.HasOne(x => x.Instance).WithMany()
425 | .HasForeignKey("InstanceId")
426 | .OnDelete(Restrict)
427 | .IsRequired();
428 | }
429 | }
430 | }
431 | }
--------------------------------------------------------------------------------
/proxmox-cloud/Data/Migrations/20211008225618_OpenStackSandbox.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace proxmox_cloud.Data.Migrations
5 | {
6 | public partial class OpenStackSandbox : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "Flavors",
12 | columns: table => new
13 | {
14 | Id = table.Column(type: "TEXT", nullable: false),
15 | Name = table.Column(type: "TEXT", nullable: false),
16 | MemoryMb = table.Column(type: "INTEGER", nullable: false),
17 | vCPUs = table.Column(type: "INTEGER", nullable: false),
18 | Disabled = table.Column(type: "INTEGER", nullable: false),
19 | IsPublic = table.Column(type: "INTEGER", nullable: false),
20 | Description = table.Column(type: "TEXT", nullable: true),
21 | CreatedAt = table.Column(type: "TEXT", nullable: false),
22 | UpdatedAt = table.Column(type: "TEXT", nullable: false)
23 | },
24 | constraints: table =>
25 | {
26 | table.PrimaryKey("PK_Flavors", x => x.Id);
27 | });
28 |
29 | migrationBuilder.CreateTable(
30 | name: "Images",
31 | columns: table => new
32 | {
33 | Id = table.Column(type: "TEXT", nullable: false),
34 | Name = table.Column(type: "TEXT", nullable: true),
35 | Size = table.Column(type: "INTEGER", nullable: false),
36 | VirtualSize = table.Column(type: "INTEGER", nullable: false),
37 | Visibility = table.Column(type: "INTEGER", nullable: false),
38 | MinDisk = table.Column(type: "INTEGER", nullable: false),
39 | MinRam = table.Column(type: "INTEGER", nullable: false),
40 | OwnerId = table.Column(type: "TEXT", nullable: false),
41 | Protected = table.Column(type: "INTEGER", nullable: false),
42 | CreatedAt = table.Column(type: "TEXT", nullable: false),
43 | UpdatedAt = table.Column(type: "TEXT", nullable: false),
44 | DeletedAt = table.Column(type: "TEXT", nullable: true)
45 | },
46 | constraints: table =>
47 | {
48 | table.PrimaryKey("PK_Images", x => x.Id);
49 | table.ForeignKey(
50 | name: "FK_Images_AspNetUsers_OwnerId",
51 | column: x => x.OwnerId,
52 | principalTable: "AspNetUsers",
53 | principalColumn: "Id",
54 | onDelete: ReferentialAction.Restrict);
55 | });
56 |
57 | migrationBuilder.CreateTable(
58 | name: "Projects",
59 | columns: table => new
60 | {
61 | Id = table.Column(type: "TEXT", nullable: false),
62 | Name = table.Column(type: "TEXT", nullable: true),
63 | Description = table.Column(type: "TEXT", nullable: true),
64 | Enabled = table.Column(type: "INTEGER", nullable: false),
65 | ParentId = table.Column(type: "TEXT", nullable: true)
66 | },
67 | constraints: table =>
68 | {
69 | table.PrimaryKey("PK_Projects", x => x.Id);
70 | table.ForeignKey(
71 | name: "FK_Projects_Projects_ParentId",
72 | column: x => x.ParentId,
73 | principalTable: "Projects",
74 | principalColumn: "Id",
75 | onDelete: ReferentialAction.Restrict);
76 | });
77 |
78 | migrationBuilder.CreateTable(
79 | name: "FlavorExtraSpecs",
80 | columns: table => new
81 | {
82 | FlavorId = table.Column(type: "TEXT", nullable: false),
83 | Key = table.Column(type: "TEXT", nullable: false),
84 | Value = table.Column(type: "TEXT", nullable: true),
85 | CreatedAt = table.Column(type: "TEXT", nullable: false),
86 | UpdatedAt = table.Column(type: "TEXT", nullable: false),
87 | DeletedAt = table.Column(type: "TEXT", nullable: true)
88 | },
89 | constraints: table =>
90 | {
91 | table.ForeignKey(
92 | name: "FK_FlavorExtraSpecs_Flavors_FlavorId",
93 | column: x => x.FlavorId,
94 | principalTable: "Flavors",
95 | principalColumn: "Id",
96 | onDelete: ReferentialAction.Cascade);
97 | });
98 |
99 | migrationBuilder.CreateTable(
100 | name: "ImageLocations",
101 | columns: table => new
102 | {
103 | ImageId = table.Column