finished files page
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
|
<PackageReference Include="WebDav.Client" Version="2.8.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
namespace BetterIServ.Backend.Controllers;
|
namespace BetterIServ.Backend.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class HelperController : Controller {
|
public class HelperController : ControllerBase {
|
||||||
|
|
||||||
[HttpPost("/login")]
|
[HttpPost("/login")]
|
||||||
public async Task<ActionResult<string>> Login([FromForm] string email, [FromForm] string password) {
|
public async Task<ActionResult<string>> Login([FromForm] string email, [FromForm] string password) {
|
||||||
@@ -34,7 +34,7 @@ public class HelperController : Controller {
|
|||||||
|
|
||||||
return Ok(part.Replace("IServAuthSession=", ""));
|
return Ok(part.Replace("IServAuthSession=", ""));
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception) {
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
115
BetterIServ.Backend/Controllers/WebDavController.cs
Normal file
115
BetterIServ.Backend/Controllers/WebDavController.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
using System.Net;
|
||||||
|
using BetterIServ.Backend.Entities;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using WebDav;
|
||||||
|
|
||||||
|
namespace BetterIServ.Backend.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("webdav")]
|
||||||
|
public class WebDavController : ControllerBase {
|
||||||
|
|
||||||
|
[HttpPost("content")]
|
||||||
|
public async Task<ActionResult<DirectoryContent[]>> GetDirContent([FromBody] Credentials credentials, [FromQuery] string dir) {
|
||||||
|
var baseAddress = new Uri($"https://webdav.{credentials.Domain}");
|
||||||
|
using var client = new WebDavClient(new WebDavClientParams {
|
||||||
|
BaseAddress = baseAddress,
|
||||||
|
Credentials = new NetworkCredential(credentials.Username, credentials.Password)
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await client.Propfind(baseAddress + dir);
|
||||||
|
if (!result.IsSuccessful) return NotFound(result.Description);
|
||||||
|
|
||||||
|
var contents = new List<DirectoryContent>();
|
||||||
|
foreach (var resource in result.Resources) {
|
||||||
|
var name = resource.Uri.Split("/")[^1];
|
||||||
|
if (resource.Uri.EndsWith("/"))
|
||||||
|
name = resource.Uri.Split("/")[^2];
|
||||||
|
|
||||||
|
var content = new DirectoryContent {
|
||||||
|
Url = resource.Uri,
|
||||||
|
LastModified = resource.LastModifiedDate ?? DateTime.Now,
|
||||||
|
Size = resource.ContentLength ?? 0,
|
||||||
|
Type = resource.IsCollection ? "dir" : "file",
|
||||||
|
Name = name
|
||||||
|
};
|
||||||
|
contents.Add(content);
|
||||||
|
}
|
||||||
|
contents.RemoveAt(0);
|
||||||
|
|
||||||
|
return contents.OrderBy(item => item.Type).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("download")]
|
||||||
|
public async Task<FileStreamResult> DonwloadFile([FromBody] Credentials credentials, [FromQuery] string url) {
|
||||||
|
var baseAddress = new Uri($"https://webdav.{credentials.Domain}");
|
||||||
|
using var client = new WebDavClient(new WebDavClientParams {
|
||||||
|
BaseAddress = baseAddress,
|
||||||
|
Credentials = new NetworkCredential(credentials.Username, credentials.Password)
|
||||||
|
});
|
||||||
|
|
||||||
|
var file = await client.GetRawFile(new Uri(baseAddress + url));
|
||||||
|
if (!file.IsSuccessful) {
|
||||||
|
Response.StatusCode = StatusCodes.Status404NotFound;
|
||||||
|
return new FileStreamResult(Stream.Null, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
var split = url.Split("/");
|
||||||
|
return new FileStreamResult(file.Stream, "application/octet-stream") {
|
||||||
|
FileDownloadName = split[^1]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("delete")]
|
||||||
|
public async Task<IActionResult> DeleteElement([FromBody] Credentials credentials, [FromQuery] string url) {
|
||||||
|
var baseAddress = new Uri($"https://webdav.{credentials.Domain}");
|
||||||
|
using var client = new WebDavClient(new WebDavClientParams {
|
||||||
|
BaseAddress = baseAddress,
|
||||||
|
Credentials = new NetworkCredential(credentials.Username, credentials.Password)
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await client.Delete(new Uri(baseAddress + url));
|
||||||
|
if (result.IsSuccessful) return Ok();
|
||||||
|
return BadRequest(result.Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("upload")]
|
||||||
|
public async Task<IActionResult> UploadFile([FromQuery] string url, [FromForm] string domain, [FromForm] string username, [FromForm] string password) {
|
||||||
|
var baseAddress = new Uri($"https://webdav.{domain}");
|
||||||
|
using var client = new WebDavClient(new WebDavClientParams {
|
||||||
|
BaseAddress = baseAddress,
|
||||||
|
Credentials = new NetworkCredential(username, password)
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await client.PutFile(new Uri(baseAddress + url), Request.Form.Files[0].OpenReadStream());
|
||||||
|
if (result.IsSuccessful) return Ok();
|
||||||
|
return BadRequest(result.Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("create")]
|
||||||
|
public async Task<IActionResult> CreateFolder([FromBody] Credentials credentials, [FromQuery] string url) {
|
||||||
|
var baseAddress = new Uri($"https://webdav.{credentials.Domain}");
|
||||||
|
using var client = new WebDavClient(new WebDavClientParams {
|
||||||
|
BaseAddress = baseAddress,
|
||||||
|
Credentials = new NetworkCredential(credentials.Username, credentials.Password)
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await client.Mkcol(new Uri(baseAddress + url));
|
||||||
|
if (result.IsSuccessful) return Ok();
|
||||||
|
return BadRequest(result.Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("move")]
|
||||||
|
public async Task<IActionResult> MoveElement([FromBody] Credentials credentials, [FromQuery] string url, [FromQuery] string newUrl) {
|
||||||
|
var baseAddress = new Uri($"https://webdav.{credentials.Domain}");
|
||||||
|
using var client = new WebDavClient(new WebDavClientParams {
|
||||||
|
BaseAddress = baseAddress,
|
||||||
|
Credentials = new NetworkCredential(credentials.Username, credentials.Password)
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await client.Move(new Uri(baseAddress + url), new Uri(baseAddress + newUrl));
|
||||||
|
if (result.IsSuccessful) return Ok();
|
||||||
|
return BadRequest(result.Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
7
BetterIServ.Backend/Entities/Credentials.cs
Normal file
7
BetterIServ.Backend/Entities/Credentials.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace BetterIServ.Backend.Entities;
|
||||||
|
|
||||||
|
public struct Credentials {
|
||||||
|
public string Domain { get; set; }
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
}
|
||||||
9
BetterIServ.Backend/Entities/DirectoryContent.cs
Normal file
9
BetterIServ.Backend/Entities/DirectoryContent.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace BetterIServ.Backend.Entities;
|
||||||
|
|
||||||
|
public struct DirectoryContent {
|
||||||
|
public string Url { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public DateTime LastModified { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
}
|
||||||
@@ -20,6 +20,8 @@ if (app.Environment.IsDevelopment()) {
|
|||||||
app.UseCors(options => {
|
app.UseCors(options => {
|
||||||
options.WithOrigins("http://localhost:8100");
|
options.WithOrigins("http://localhost:8100");
|
||||||
options.AllowCredentials();
|
options.AllowCredentials();
|
||||||
|
options.AllowAnyHeader();
|
||||||
|
options.AllowAnyMethod();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|||||||
42
BetterIServ.Mobile/package-lock.json
generated
42
BetterIServ.Mobile/package-lock.json
generated
@@ -14,12 +14,15 @@
|
|||||||
"@angular/platform-browser": "^15.0.0",
|
"@angular/platform-browser": "^15.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^15.0.0",
|
"@angular/platform-browser-dynamic": "^15.0.0",
|
||||||
"@angular/router": "^15.0.0",
|
"@angular/router": "^15.0.0",
|
||||||
|
"@awesome-cordova-plugins/file": "^6.3.0",
|
||||||
"@capacitor/app": "4.1.1",
|
"@capacitor/app": "4.1.1",
|
||||||
"@capacitor/core": "4.7.3",
|
"@capacitor/core": "4.7.3",
|
||||||
"@capacitor/haptics": "4.1.0",
|
"@capacitor/haptics": "4.1.0",
|
||||||
"@capacitor/keyboard": "4.1.1",
|
"@capacitor/keyboard": "4.1.1",
|
||||||
"@capacitor/status-bar": "4.1.1",
|
"@capacitor/status-bar": "4.1.1",
|
||||||
"@ionic/angular": "^7.0.0",
|
"@ionic/angular": "^7.0.0",
|
||||||
|
"@types/file-saver": "^2.0.5",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"ionicons": "^7.0.0",
|
"ionicons": "^7.0.0",
|
||||||
"rxjs": "~7.5.0",
|
"rxjs": "~7.5.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
@@ -692,6 +695,30 @@
|
|||||||
"integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==",
|
"integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@awesome-cordova-plugins/core": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/core/-/core-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-MkcWO8akZLHa2RSJEPf76Y3P9wPqh5oXE8YCzn2vnYYeNyYWYnka2pHFsgUdbXJNiS+YeveUzvw+Isweg+wynA==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/cordova": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@awesome-cordova-plugins/file": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/file/-/file-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-w2S/X/pr0Edl8+O/ndIIlnikwkD1XEMM/8TQFp/AI1riqJFyPtYNgnU54iRjagIRJE+GRyydaUnQXp5DVn9Htg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/cordova": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@awesome-cordova-plugins/core": "^6.0.1",
|
||||||
|
"rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.21.4",
|
"version": "7.21.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
|
||||||
@@ -3598,6 +3625,11 @@
|
|||||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
|
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cordova": {
|
||||||
|
"version": "11.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-11.0.0.tgz",
|
||||||
|
"integrity": "sha512-AtBm1IAqqXsXszJe6XxuA2iXLhraNCj25p/FHRyikPeW0Z3YfgM6qzWb+VJglJTmZc5lqRNy84cYM/sQI5v6Vw=="
|
||||||
|
},
|
||||||
"node_modules/@types/cors": {
|
"node_modules/@types/cors": {
|
||||||
"version": "2.8.13",
|
"version": "2.8.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
|
||||||
@@ -3656,6 +3688,11 @@
|
|||||||
"@types/range-parser": "*"
|
"@types/range-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/file-saver": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ=="
|
||||||
|
},
|
||||||
"node_modules/@types/fs-extra": {
|
"node_modules/@types/fs-extra": {
|
||||||
"version": "8.1.2",
|
"version": "8.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz",
|
||||||
@@ -7306,6 +7343,11 @@
|
|||||||
"node": "^10.12.0 || >=12.0.0"
|
"node": "^10.12.0 || >=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/file-saver": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
|
|||||||
@@ -20,12 +20,14 @@
|
|||||||
"@angular/platform-browser": "^15.0.0",
|
"@angular/platform-browser": "^15.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^15.0.0",
|
"@angular/platform-browser-dynamic": "^15.0.0",
|
||||||
"@angular/router": "^15.0.0",
|
"@angular/router": "^15.0.0",
|
||||||
|
"@awesome-cordova-plugins/file": "^6.3.0",
|
||||||
"@capacitor/app": "4.1.1",
|
"@capacitor/app": "4.1.1",
|
||||||
"@capacitor/core": "4.7.3",
|
"@capacitor/core": "4.7.3",
|
||||||
"@capacitor/haptics": "4.1.0",
|
"@capacitor/haptics": "4.1.0",
|
||||||
"@capacitor/keyboard": "4.1.1",
|
"@capacitor/keyboard": "4.1.1",
|
||||||
"@capacitor/status-bar": "4.1.1",
|
"@capacitor/status-bar": "4.1.1",
|
||||||
"@ionic/angular": "^7.0.0",
|
"@ionic/angular": "^7.0.0",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"ionicons": "^7.0.0",
|
"ionicons": "^7.0.0",
|
||||||
"rxjs": "~7.5.0",
|
"rxjs": "~7.5.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
@@ -46,6 +48,7 @@
|
|||||||
"@ionic/angular-toolkit": "^9.0.0",
|
"@ionic/angular-toolkit": "^9.0.0",
|
||||||
"@types/jasmine": "~4.0.0",
|
"@types/jasmine": "~4.0.0",
|
||||||
"@types/node": "^12.11.1",
|
"@types/node": "^12.11.1",
|
||||||
|
"@types/file-saver": "^2.0.5",
|
||||||
"@typescript-eslint/eslint-plugin": "5.3.0",
|
"@typescript-eslint/eslint-plugin": "5.3.0",
|
||||||
"@typescript-eslint/parser": "5.3.0",
|
"@typescript-eslint/parser": "5.3.0",
|
||||||
"eslint": "^7.6.0",
|
"eslint": "^7.6.0",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {firstValueFrom} from "rxjs";
|
|||||||
export class IServService {
|
export class IServService {
|
||||||
|
|
||||||
public userdata?: Userdata;
|
public userdata?: Userdata;
|
||||||
private backend: string = "http://localhost:5273";
|
public backend: string = "http://localhost:5273";
|
||||||
|
|
||||||
constructor(private client: HttpClient) {
|
constructor(private client: HttpClient) {
|
||||||
const data = localStorage.getItem("userdata");
|
const data = localStorage.getItem("userdata");
|
||||||
52
BetterIServ.Mobile/src/app/api/webdav.service.ts
Normal file
52
BetterIServ.Mobile/src/app/api/webdav.service.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {IServService} from "./iserv.service";
|
||||||
|
import {HttpClient, HttpEvent} from "@angular/common/http";
|
||||||
|
import {DirectoryContent} from "../entities/directoryContent";
|
||||||
|
import {firstValueFrom, Observable} from "rxjs";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class WebdavService {
|
||||||
|
|
||||||
|
constructor(private iserv: IServService, private client: HttpClient) {}
|
||||||
|
|
||||||
|
public async getDirectory(path: string): Promise<DirectoryContent[]> {
|
||||||
|
const contents = await firstValueFrom(this.client.post<DirectoryContent[]>(this.iserv.backend + "/webdav/content?dir=" + path, this.iserv.userdata));
|
||||||
|
|
||||||
|
for (let content of contents) {
|
||||||
|
content.name = decodeURIComponent(content.name);
|
||||||
|
content.url = decodeURI(content.url);
|
||||||
|
content.lastModified = new Date(content.lastModified);
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public downloadFile(url: string): Observable<HttpEvent<Blob>> {
|
||||||
|
return this.client.post(this.iserv.backend + "/webdav/download?url=" + url, this.iserv.userdata, {responseType: "blob", reportProgress: true, observe: "events"});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(url: string) {
|
||||||
|
await firstValueFrom(this.client.post(this.iserv.backend + "/webdav/delete?url=" + url, this.iserv.userdata));
|
||||||
|
}
|
||||||
|
|
||||||
|
public uploadFile(url: string, file: File): Observable<HttpEvent<any>> {
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('username', this.iserv.userdata.username);
|
||||||
|
form.append('password', this.iserv.userdata.password);
|
||||||
|
form.append('domain', this.iserv.userdata.domain);
|
||||||
|
form.append('file', file);
|
||||||
|
|
||||||
|
return this.client.post(this.iserv.backend + "/webdav/upload?url=" + url, form, {reportProgress: true, observe: "events"});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createFolder(url: string) {
|
||||||
|
await firstValueFrom(this.client.post(this.iserv.backend + "/webdav/create/?url=" + url, this.iserv.userdata));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async moveElement(url: string, newUrl: string) {
|
||||||
|
await firstValueFrom(this.client.post(this.iserv.backend + `/webdav/move/?url=${url}&newUrl=${newUrl}`, this.iserv.userdata));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<ion-app>
|
<ion-app>
|
||||||
<ion-split-pane contentId="main-content">
|
<ion-split-pane contentId="main-content">
|
||||||
<ion-menu contentId="main-content" type="overlay">
|
<ion-menu contentId="main-content" type="overlay" *ngIf="router.url != '/login'">
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-list-header>BetterIServ</ion-list-header>
|
<ion-list-header>BetterIServ</ion-list-header>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</ion-menu-toggle>
|
</ion-menu-toggle>
|
||||||
|
|
||||||
<ion-menu-toggle auto-hide="false">
|
<ion-menu-toggle auto-hide="false">
|
||||||
<ion-item lines="none" detail="false" (click)="logout()">
|
<ion-item lines="none" detail="false" routerDirection="root" routerLinkActive="selected" [routerLink]="'logout'" (click)="logout()">
|
||||||
<ion-icon aria-hidden="true" slot="start" ios="log-out-outline" md="log-out-sharp"></ion-icon>
|
<ion-icon aria-hidden="true" slot="start" ios="log-out-outline" md="log-out-sharp"></ion-icon>
|
||||||
<ion-label>Ausloggen</ion-label>
|
<ion-label>Ausloggen</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import {Router, RouterLink, RouterLinkActive} from '@angular/router';
|
import {Router, RouterLink, RouterLinkActive} from '@angular/router';
|
||||||
import { IonicModule } from '@ionic/angular';
|
import { IonicModule } from '@ionic/angular';
|
||||||
import {IServService} from "./Api/iserv.service";
|
import {IServService} from "./api/iserv.service";
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: 'app.component.html',
|
templateUrl: 'app.component.html',
|
||||||
@@ -22,7 +22,7 @@ export class AppComponent {
|
|||||||
];
|
];
|
||||||
public email = "leon.hoppe@hgbp.de";
|
public email = "leon.hoppe@hgbp.de";
|
||||||
|
|
||||||
constructor(private router: Router, public iserv: IServService) {
|
constructor(public router: Router, public iserv: IServService) {
|
||||||
if (localStorage.getItem("userdata") == null) {
|
if (localStorage.getItem("userdata") == null) {
|
||||||
this.router.navigate(["login"]);
|
this.router.navigate(["login"]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,14 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'home',
|
path: 'home',
|
||||||
loadComponent: () => import('./home/home.page').then( m => m.HomePage)
|
loadComponent: () => import('./pages/home/home.page').then(m => m.HomePage)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
loadComponent: () => import('./login/login.page').then( m => m.LoginPage)
|
loadComponent: () => import('./pages/login/login.page').then(m => m.LoginPage)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'files',
|
||||||
|
loadComponent: () => import('./pages/files/files.page').then( m => m.FilesPage)
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
7
BetterIServ.Mobile/src/app/entities/directoryContent.ts
Normal file
7
BetterIServ.Mobile/src/app/entities/directoryContent.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export interface DirectoryContent {
|
||||||
|
url: string,
|
||||||
|
name: string,
|
||||||
|
lastModified: Date,
|
||||||
|
type: string,
|
||||||
|
size: number
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
ion-menu-button {
|
|
||||||
color: var(--ion-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
#container {
|
|
||||||
text-align: center;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#container strong {
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 26px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container p {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 22px;
|
|
||||||
color: #8c8c8c;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
95
BetterIServ.Mobile/src/app/pages/files/files.page.html
Normal file
95
BetterIServ.Mobile/src/app/pages/files/files.page.html
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<ion-header [translucent]="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-menu-button></ion-menu-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>Dateien</ion-title>
|
||||||
|
<ion-progress-bar type="indeterminate" *ngIf="loading" />
|
||||||
|
<ion-progress-bar [value]="progress" *ngIf="progress != -1" />
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content [fullscreen]="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Dateien</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-grid>
|
||||||
|
<ion-row>
|
||||||
|
<ion-col>
|
||||||
|
<ion-button (click)="upload.click()"><ion-icon ios="arrow-up-circle-outline" md="arrow-up-circle-sharp" /></ion-button>
|
||||||
|
<ion-button id="create-folder"><ion-icon ios="add-circle-outline" md="add-circle-sharp" ></ion-icon></ion-button>
|
||||||
|
<form #uploadForm>
|
||||||
|
<input type="file" hidden="hidden" multiple (change)="onUpload(upload.files, uploadForm)" #upload>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ion-modal trigger="create-folder" (willDismiss)="createFolder($event)" #modal>
|
||||||
|
<ng-template>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-button (click)="modal.dismiss(null, 'cancel')">Abbrechen</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>Neuer Ordner</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="modal.dismiss(newFolder.value, 'confirm')" [strong]="true">Erstellen</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<ion-item>
|
||||||
|
<ion-label position="stacked">Name</ion-label>
|
||||||
|
<ion-input aria-label="Name" type="text" #newFolder/>
|
||||||
|
</ion-item>
|
||||||
|
</ion-content>
|
||||||
|
</ng-template>
|
||||||
|
</ion-modal>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col *ngIf="clipboard">
|
||||||
|
<ion-button (click)="onMove()">Verschieben</ion-button>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
|
||||||
|
<section class="container">
|
||||||
|
<ion-list>
|
||||||
|
<ion-list-header>{{currentDirectory}}</ion-list-header>
|
||||||
|
|
||||||
|
<ion-item *ngIf="currentDirectory != '/'">
|
||||||
|
<ion-icon ios="folder-outline" md="folder-sharp"></ion-icon>
|
||||||
|
<ion-label (click)="goUpFolder()">Übergeordnetes Verzeichnis</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item *ngFor="let item of directoryContent">
|
||||||
|
<ion-icon [ios]="(item.type == 'dir' ? 'folder' : 'document') + '-outline'" [md]="(item.type == 'dir' ? 'folder' : 'document') + '-sharp'" />
|
||||||
|
<ion-label (click)="interact(item)">{{item.name}}</ion-label>
|
||||||
|
<ion-button fill="none" (click)="openMenu(item)">
|
||||||
|
<ion-icon ios="ellipsis-vertical-outline" md="ellipsis-vertical-sharp" />
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</section>
|
||||||
|
</ion-content>
|
||||||
|
|
||||||
|
<ion-footer>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-grid>
|
||||||
|
<ion-row>
|
||||||
|
<ion-col>
|
||||||
|
<ion-tab-button (click)="switchDirectory('/Files')" [ngClass]="{'active': currentDirectory.startsWith('/Files')}">
|
||||||
|
<ion-icon ios="folder-outline" md="folder-sharp" />
|
||||||
|
Eigene
|
||||||
|
</ion-tab-button>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<ion-tab-button (click)="switchDirectory('/Groups')" [ngClass]="{'active': currentDirectory.startsWith('/Groups')}">
|
||||||
|
<ion-icon ios="people-outline" md="people-sharp" />
|
||||||
|
Gruppen
|
||||||
|
</ion-tab-button>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
8
BetterIServ.Mobile/src/app/pages/files/files.page.scss
Normal file
8
BetterIServ.Mobile/src/app/pages/files/files.page.scss
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
ion-label {
|
||||||
|
margin-left: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
color: var(--ion-color-primary);
|
||||||
|
}
|
||||||
173
BetterIServ.Mobile/src/app/pages/files/files.page.ts
Normal file
173
BetterIServ.Mobile/src/app/pages/files/files.page.ts
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {ActionSheetController, AlertController, IonicModule, Platform} from '@ionic/angular';
|
||||||
|
import {WebdavService} from "../../api/webdav.service";
|
||||||
|
import {DirectoryContent} from "../../entities/directoryContent";
|
||||||
|
import {File} from "@awesome-cordova-plugins/file/ngx";
|
||||||
|
import {saveAs} from "file-saver";
|
||||||
|
import {HttpDownloadProgressEvent, HttpEventType} from "@angular/common/http";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-files',
|
||||||
|
templateUrl: './files.page.html',
|
||||||
|
styleUrls: ['./files.page.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [IonicModule, CommonModule, FormsModule]
|
||||||
|
})
|
||||||
|
export class FilesPage implements OnInit {
|
||||||
|
|
||||||
|
public currentDirectory: string = "/Files/";
|
||||||
|
public directoryContent: DirectoryContent[];
|
||||||
|
public clipboard: DirectoryContent = undefined;
|
||||||
|
public loading: boolean = true;
|
||||||
|
|
||||||
|
public progress: number = -1;
|
||||||
|
|
||||||
|
constructor(private webdav: WebdavService, private platform: Platform, private menus: ActionSheetController, private alerts: AlertController) { }
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.directoryContent = await this.webdav.getDirectory(this.currentDirectory);
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async switchDirectory(dir: string) {
|
||||||
|
this.loading = true;
|
||||||
|
this.directoryContent = await this.webdav.getDirectory(dir);
|
||||||
|
this.currentDirectory = dir;
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async goUpFolder() {
|
||||||
|
const split = this.currentDirectory.split("/");
|
||||||
|
await this.switchDirectory(this.currentDirectory.replace(split[split.length - 2] + "/", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async interact(item: DirectoryContent) {
|
||||||
|
if (item.type == "dir") {
|
||||||
|
await this.switchDirectory(item.url);
|
||||||
|
}else {
|
||||||
|
this.webdav.downloadFile(item.url).subscribe(async event => {
|
||||||
|
if (event.type == HttpEventType.DownloadProgress) {
|
||||||
|
const e = event as HttpDownloadProgressEvent;
|
||||||
|
this.progress = e.loaded / e.total * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type == HttpEventType.Response) {
|
||||||
|
const blob = event.body;
|
||||||
|
const file = new File();
|
||||||
|
|
||||||
|
if (this.platform.is('desktop')) {
|
||||||
|
saveAs(blob, item.name);
|
||||||
|
this.progress = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadPath = (
|
||||||
|
this.platform.is('android')
|
||||||
|
) ? file.externalDataDirectory : file.documentsDirectory;
|
||||||
|
await file.writeFile(downloadPath, item.name, blob, {replace: true});
|
||||||
|
this.progress = -1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async openMenu(item: DirectoryContent) {
|
||||||
|
const menu = await this.menus.create({
|
||||||
|
header: item.name,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: "Löschen",
|
||||||
|
role: "destructive",
|
||||||
|
data: {action: "delete"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Verschieben",
|
||||||
|
data: {action: "move"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Abbrechen",
|
||||||
|
role: "cancel",
|
||||||
|
data: {action: "cancel"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
await menu.present();
|
||||||
|
const result = await menu.onDidDismiss<{action: string}>();
|
||||||
|
|
||||||
|
if (result.data?.action == undefined) return;
|
||||||
|
if (result.data.action == "delete") {
|
||||||
|
const alert = await this.alerts.create({
|
||||||
|
subHeader: "Möchtest du dieses Element wirklich löschen?",
|
||||||
|
message: item.name,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: "Abbrechen",
|
||||||
|
role: "cancel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Löschen",
|
||||||
|
role: "destructive"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
await alert.present();
|
||||||
|
const result = await alert.onDidDismiss();
|
||||||
|
if (result.role == "destructive") {
|
||||||
|
this.loading = true;
|
||||||
|
await this.webdav.delete(item.url);
|
||||||
|
await this.switchDirectory(this.currentDirectory);
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
await (await this.alerts.create({
|
||||||
|
header: "Element gelöscht!",
|
||||||
|
buttons: ["Ok"]
|
||||||
|
})).present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.data.action == "move") {
|
||||||
|
this.clipboard = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onUpload(files: FileList, form: HTMLFormElement) {
|
||||||
|
this.loading = true;
|
||||||
|
const uploads: Promise<void>[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files.item(i);
|
||||||
|
uploads.push(new Promise<void>(resolve => {
|
||||||
|
this.webdav.uploadFile(this.currentDirectory + file.name, file).subscribe(event => {
|
||||||
|
if (event.type == HttpEventType.Response) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(uploads);
|
||||||
|
await this.switchDirectory(this.currentDirectory);
|
||||||
|
this.loading = false;
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createFolder(event: any) {
|
||||||
|
if (event.detail.data == null) return;
|
||||||
|
this.loading = true;
|
||||||
|
await this.webdav.createFolder(this.currentDirectory + event.detail.data);
|
||||||
|
await this.switchDirectory(this.currentDirectory);
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onMove() {
|
||||||
|
this.loading = true;
|
||||||
|
await this.webdav.moveElement(this.clipboard.url, this.currentDirectory + this.clipboard.name);
|
||||||
|
await this.switchDirectory(this.currentDirectory);
|
||||||
|
this.loading = false;
|
||||||
|
delete this.clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,9 +13,4 @@
|
|||||||
<ion-title size="large">Übersicht</ion-title>
|
<ion-title size="large">Übersicht</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<div id="container">
|
|
||||||
<strong class="capitalize">Übersicht</strong>
|
|
||||||
<p>Explore <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
|
|
||||||
</div>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
1
BetterIServ.Mobile/src/app/pages/home/home.page.scss
Normal file
1
BetterIServ.Mobile/src/app/pages/home/home.page.scss
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import {AlertController, IonicModule} from '@ionic/angular';
|
import {AlertController, IonicModule} from '@ionic/angular';
|
||||||
import {IServService} from "../Api/iserv.service";
|
import {IServService} from "../../api/iserv.service";
|
||||||
import {Router} from "@angular/router";
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -24,3 +24,7 @@
|
|||||||
@import "@ionic/angular/css/text-alignment.css";
|
@import "@ionic/angular/css/text-alignment.css";
|
||||||
@import "@ionic/angular/css/text-transformation.css";
|
@import "@ionic/angular/css/text-transformation.css";
|
||||||
@import "@ionic/angular/css/flex-utils.css";
|
@import "@ionic/angular/css/flex-utils.css";
|
||||||
|
|
||||||
|
ion-menu-button {
|
||||||
|
color: var(--ion-color-primary);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"outDir": "./dist/out-tsc",
|
"outDir": "./dist/out-tsc",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": true,
|
"strict": false,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
"noPropertyAccessFromIndexSignature": true,
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user