add ability to import users

This commit is contained in:
sbwalker
2023-09-20 10:43:49 -04:00
parent 886df69058
commit 8e5e79a799
11 changed files with 421 additions and 6 deletions

View File

@ -20,8 +20,9 @@ else
<div class="container">
<div class="row mb-1 align-items-center">
<div class="col-sm-4">
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
</div>
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />&nbsp;
<ActionLink Text="Import Users" Class="btn btn-secondary" Action="Users" Security="SecurityAccessLevel.Admin" />
</div>
<div class="col-sm-4">
<input class="form-control" @bind="@_search" />
</div>
@ -54,7 +55,7 @@ else
<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td>
</Row>
</Pager>
</TabPanel>
</TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
<div class="container">
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
@ -360,7 +361,7 @@ else
</div>
<br />
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
</TabPanel>
</TabPanel>
</TabStrip>
}

View File

@ -0,0 +1,55 @@
@namespace Oqtane.Modules.Admin.Users
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IStringLocalizer<Users> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="userfile" HelpText="Select or upload a CSV file containing user information. The CSV file must be in the Template format specified." ResourceKey="UserFile">User File:</Label>
<div class="col-sm-9">
<FileManager Id="userfile" @ref="_filemanager" Filter="csv" />
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-success" @onclick="ImportUsers">@Localizer["Import"]</button>&nbsp;
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>&nbsp;
<a class="btn btn-info" href="/users.csv" target="_new">@Localizer["Template"]</a>
@code {
private FileManager _filemanager;
public override string Title => "Import Users";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private async Task ImportUsers()
{
try
{
var fileid = _filemanager.GetFileId();
if (fileid != -1)
{
if (await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid))
{
AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["Message.Import.Validation"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Importing Users {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Import"], MessageType.Error);
}
}
}

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="UserFile.HelpText" xml:space="preserve">
<value>Select or upload a CSV file containing user information. The CSV file must be in the Template format specified.</value>
</data>
<data name="UserFile.Text" xml:space="preserve">
<value>User File:</value>
</data>
<data name="Error.Import" xml:space="preserve">
<value>Error Importing Users</value>
</data>
<data name="Import" xml:space="preserve">
<value>Import</value>
</data>
<data name="Message.Import.Failure" xml:space="preserve">
<value>User Import Failed</value>
</data>
<data name="Message.Import.Success" xml:space="preserve">
<value>Users Imported Successfully</value>
</data>
<data name="Message.Import.Validation" xml:space="preserve">
<value>You Must Specify A User File For Import</value>
</data>
<data name="Template" xml:space="preserve">
<value>Template</value>
</data>
</root>

View File

@ -142,5 +142,12 @@ namespace Oqtane.Services
/// <param name="siteId">ID of a <see cref="Site"/></param>
/// <returns></returns>
Task<string> GetPasswordRequirementsAsync(int siteId);
/// <summary>
/// Bulk import of users
/// </summary>
/// <param name="fileId">ID of a <see cref="File"/></param>
/// <returns></returns>
Task<bool> ImportUsersAsync(int siteId, int fileId);
}
}

View File

@ -6,6 +6,9 @@ using Oqtane.Documentation;
using System.Net;
using System.Collections.Generic;
using Microsoft.Extensions.Localization;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Oqtane.Modules.Admin.Roles;
using System.Xml.Linq;
namespace Oqtane.Services
{
@ -123,5 +126,10 @@ namespace Oqtane.Services
// format requirements
return string.Format(passwordValidationCriteriaTemplate, minimumlength, uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement);
}
public async Task<bool> ImportUsersAsync(int siteId, int fileId)
{
return await PostJsonAsync<bool>($"{Apiurl}/import?siteid={siteId}&fileid={fileId}", true);
}
}
}