diff options
112 files changed, 2260 insertions, 770 deletions
diff --git a/backend/api/api/Controllers/AuthController.cs b/backend/api/api/Controllers/AuthController.cs index 6dfe483a..7167d1bf 100644 --- a/backend/api/api/Controllers/AuthController.cs +++ b/backend/api/api/Controllers/AuthController.cs @@ -49,8 +49,6 @@ namespace api.Controllers return Ok(newToken); - - } diff --git a/backend/api/api/Controllers/DatasetController.cs b/backend/api/api/Controllers/DatasetController.cs index d022d6d2..3d008744 100644 --- a/backend/api/api/Controllers/DatasetController.cs +++ b/backend/api/api/Controllers/DatasetController.cs @@ -18,32 +18,32 @@ namespace api.Controllers } - // GET: api/<DatasetController>/{id}/datasets - [HttpGet("{id}/datasets")] - public ActionResult<List<Dataset>> Get(string id) + // GET: api/<DatasetController>/{username}/datasets + [HttpGet("{username}/datasets")] + public ActionResult<List<Dataset>> Get(string username) { - return _datasetService.GetAllDatesets(id); + return _datasetService.GetAllDatesets(username); } - // GET api/<DatasetController>/{id}/{name} - [HttpGet("{id}/{name}")] - public ActionResult<Dataset> Get(string id, string name) + // GET api/<DatasetController>/{username}/{name} + [HttpGet("{username}/{name}")] + public ActionResult<Dataset> Get(string username, string name) { - var dataset = _datasetService.GetOneDataset(id, name); + var dataset = _datasetService.GetOneDataset(username, name); if (dataset == null) - return NotFound($"Dataset with name = {name} not found"); + return NotFound($"Dataset with name = {name} or user with username = {username} not found"); return dataset; } - // POST api/<DatasetController> - [HttpPost("post")] + // POST api/<DatasetController>/add + [HttpPost("add")] public ActionResult<Dataset> Post([FromBody] Dataset dataset) { - var existingUser = _datasetService.GetOneDataset(dataset.uploaderId,dataset.name); + var existingDataset = _datasetService.GetOneDataset(dataset.username, dataset.name); - if (existingUser != null) + if (existingDataset != null) return NotFound($"Dateset with name = {dataset.name} exisits"); else { @@ -53,30 +53,30 @@ namespace api.Controllers } } - // PUT api/<DatasetController>/5 - [HttpPut("{id}/{name}")] - public ActionResult Put(string id, string name, [FromBody] Dataset dataset) + // PUT api/<DatasetController>/{username}/{name} + [HttpPut("{username}/{name}")] + public ActionResult Put(string username, string name, [FromBody] Dataset dataset) { - var existingDataset = _datasetService.GetOneDataset(id, name); + var existingDataset = _datasetService.GetOneDataset(username, name); //ne mora da se proverava if (existingDataset == null) - return NotFound($"Dataset with name = {name} not found"); + return NotFound($"Dataset with name = {name} or user with username = {username} not found"); - _datasetService.Update(id, name, dataset); + _datasetService.Update(username, name, dataset); return NoContent(); } - // DELETE api/<DatasetController>/5 - [HttpDelete("{id}")] - public ActionResult Delete(string id, string name) + // DELETE api/<DatasetController>/username/name + [HttpDelete("{username}/{name}")] + public ActionResult Delete(string username, string name) { - var dataset = _datasetService.GetOneDataset(id, name); + var dataset = _datasetService.GetOneDataset(username, name); if (dataset == null) - return NotFound($"Dataset with name = {name} not found"); + return NotFound($"Dataset with name = {name} or user with username = {username} not found"); - _datasetService.Delete(dataset.uploaderId,dataset.name); + _datasetService.Delete(dataset.username, dataset.name); return Ok($"Dataset with name = {name} deleted"); diff --git a/backend/api/api/Controllers/FileController.cs b/backend/api/api/Controllers/FileController.cs new file mode 100644 index 00000000..3bfdad93 --- /dev/null +++ b/backend/api/api/Controllers/FileController.cs @@ -0,0 +1,112 @@ +using System.Net.Http.Headers; +using api.Models; +using api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; +namespace api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class FileController : ControllerBase + { + private string[] permittedExtensions = { ".csv" }; + private readonly IConfiguration _configuration; + private JwtToken _token; + private IFileService _fileservice; + public FileController(IConfiguration configuration,IFileService fileService) + { + _configuration = configuration; + _token = new JwtToken(configuration); + _fileservice = fileService; + + } + + + [HttpPost("Csv")] + [Authorize(Roles = "User")] + public async Task<ActionResult<string>> CsvUpload([FromForm]IFormFile file) + { + + //get username from jwtToken + string username; + var header = Request.Headers[HeaderNames.Authorization]; + if (AuthenticationHeaderValue.TryParse(header, out var headerValue)) + { + + var scheme = headerValue.Scheme; + var parameter = headerValue.Parameter; + username = _token.TokenToUsername(parameter); + if (username == null) + return null; + }else + return BadRequest(); + + + //Check filetype + var filename=file.FileName; + var ext=Path.GetExtension(filename).ToLowerInvariant(); + var name = Path.GetFileNameWithoutExtension(filename).ToLowerInvariant(); + if (string.IsNullOrEmpty(ext) || ! permittedExtensions.Contains(ext)) { + return BadRequest("Wrong file type"); + } + var folderPath=Path.Combine(Directory.GetCurrentDirectory(),"UploadedFiles",username); + //Check Directory + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + //Index file if same filename + var fullPath = Path.Combine(folderPath, filename); + int i=0; + + while (System.IO.File.Exists(fullPath)) { + i++; + fullPath = Path.Combine(folderPath,name+i.ToString()+ext); + } + + + //Write file + using (var stream=new FileStream(fullPath, FileMode.Create)) + { + await file.CopyToAsync(stream); + } + FileModel fileModel= new FileModel(); + fileModel.path=fullPath; + fileModel.username=username; + fileModel=_fileservice.Create(fileModel); + + return Ok(fileModel); + } + + [HttpGet("Download")] + [Authorize(Roles = "User")] + public async Task<ActionResult> DownloadFile(string id) + { + //Get Username + string username; + var header = Request.Headers[HeaderNames.Authorization]; + if (AuthenticationHeaderValue.TryParse(header, out var headerValue)) + { + + var scheme = headerValue.Scheme; + var parameter = headerValue.Parameter; + username = _token.TokenToUsername(parameter); + if (username == null) + return null; + } + else + return BadRequest(); + + string filePath = _fileservice.GetFilePath(id, username); + if (filePath == null) + return BadRequest(); + + return File(System.IO.File.ReadAllBytes(filePath),"application/octet-stream", Path.GetFileName(filePath)); + + } + + + + } +} diff --git a/backend/api/api/Controllers/FileUploadController.cs b/backend/api/api/Controllers/FileUploadController.cs deleted file mode 100644 index 46e7f4f9..00000000 --- a/backend/api/api/Controllers/FileUploadController.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -namespace api.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class FileUploadController : ControllerBase - { - private string[] permittedExtensions = { ".csv" }; - - - [HttpPost("Csv")] - [Authorize(Roles = "User")] - public async Task<ActionResult<string>> CsvUpload([FromForm]IFormFile file,[FromForm]string username)//???Umesto username poslati jwt odakle se moze preuzeti username radi sigurnosti - { - var filename=file.FileName; - var ext=Path.GetExtension(filename).ToLowerInvariant(); - var name = Path.GetFileNameWithoutExtension(filename).ToLowerInvariant(); - if (string.IsNullOrEmpty(ext) || ! permittedExtensions.Contains(ext)) { - return BadRequest("Wrong file type"); - } - var folderPath=Path.Combine(Directory.GetCurrentDirectory(),"UploadedFiles",username); - if (!Directory.Exists(folderPath)) - { - Directory.CreateDirectory(folderPath); - } - - var fullPath = Path.Combine(folderPath, filename); - int i=0; - - while (System.IO.File.Exists(fullPath)) { - i++; - fullPath = Path.Combine(folderPath,name+i.ToString()+ext); - } - - - - using (var stream=new FileStream(fullPath, FileMode.Create)) - { - await file.CopyToAsync(stream); - } - - return Ok(); - } - } -} diff --git a/backend/api/api/Controllers/ModelController.cs b/backend/api/api/Controllers/ModelController.cs new file mode 100644 index 00000000..deb622b8 --- /dev/null +++ b/backend/api/api/Controllers/ModelController.cs @@ -0,0 +1,97 @@ +using api.Models; +using api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ModelController : ControllerBase + { + + private IMlConnectionService _mlService; + private readonly IModelService _modelService; + + + public ModelController(IMlConnectionService mlService, IModelService modelService) + { + _mlService = mlService; + _modelService = modelService; + } + + [HttpPost("sendModel")] + [Authorize(Roles = "User")] + public async Task<ActionResult<string>> Test([FromBody] object model) + { + var result = await _mlService.SendModelAsync(model); + return Ok(result); + } + + // GET: api/<ModelController>/{username}/models + [HttpGet("{username}/models")] + public ActionResult<List<Model>> Get(string username) + { + return _modelService.GetAllModels(username); + } + + //id korisnika, name modela + // GET api/<ModelController>/{username}/{name} + [HttpGet("{username}/{name}")] + public ActionResult<Model> Get(string username, string name) + { + var model = _modelService.GetOneModel(username, name); + + if (model == null) + return NotFound($"Model with name = {name} or user with username = {username} not found"); + + return model; + } + + // POST api/<ModelController>/add + [HttpPost("add")] + public ActionResult<Model> Post([FromBody] Model model) + { + var existingModel = _modelService.GetOneModel(model.username, model.name); + + if (existingModel != null) + return NotFound($"Model with name = {model.name} exisits"); + else + { + _modelService.Create(model); + + return CreatedAtAction(nameof(Get), new { id = model._id }, model); + } + } + + // PUT api/<ModelController>/{username}/{name} + [HttpPut("{username}/{name}")] + public ActionResult Put(string username, string name, [FromBody] Model model) + { + var existingModel = _modelService.GetOneModel(username, name); + + if (existingModel == null) + return NotFound($"Model with name = {name} or user with username = {username} not found"); + + _modelService.Update(username, name, model); + return NoContent(); + } + + // DELETE api/<ModelController>/username + [HttpDelete("{username}/{name}")] + public ActionResult Delete(string username, string name) + { + var model = _modelService.GetOneModel(username, name); + + if (model == null) + return NotFound($"Model with name = {name} or user with username = {username} not found"); + + _modelService.Delete(model.username, model.name); + + return Ok($"Model with name = {name} deleted"); + + } + + } +} diff --git a/backend/api/api/Controllers/UserController.cs b/backend/api/api/Controllers/UserController.cs index d41a42e3..58121656 100644 --- a/backend/api/api/Controllers/UserController.cs +++ b/backend/api/api/Controllers/UserController.cs @@ -1,5 +1,6 @@ using api.Models; using api.Services; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; @@ -72,6 +73,7 @@ namespace api.Controllers // PUT api/<UserController>/5 [HttpPut("{id}")] + [Authorize(Roles = "User")] public ActionResult Put(string id, [FromBody] User user) { var existingUser = userService.Get(id); @@ -86,6 +88,7 @@ namespace api.Controllers // DELETE api/<UserController>/5 [HttpDelete("{id}")] + [Authorize(Roles = "User")] public ActionResult Delete(string id) { var user = userService.Get(id); diff --git a/backend/api/api/Data/UserStoreDatabaseSettings.cs b/backend/api/api/Data/UserStoreDatabaseSettings.cs index 0d923fc7..6416ab05 100644 --- a/backend/api/api/Data/UserStoreDatabaseSettings.cs +++ b/backend/api/api/Data/UserStoreDatabaseSettings.cs @@ -10,5 +10,7 @@ namespace api.Data public string DatabaseName { get; set; } = String.Empty; public string CollectionName { get; set; } = String.Empty; public string DatasetCollectionName { get; set; } = String.Empty; + public string ModelCollectionName { get; set; } = String.Empty; + public string FilesCollectionName { get; set; } = String.Empty; } } diff --git a/backend/api/api/Interfaces/IUserStoreDatabaseSettings.cs b/backend/api/api/Interfaces/IUserStoreDatabaseSettings.cs index 8d2a175f..82312649 100644 --- a/backend/api/api/Interfaces/IUserStoreDatabaseSettings.cs +++ b/backend/api/api/Interfaces/IUserStoreDatabaseSettings.cs @@ -6,5 +6,7 @@ string DatabaseName { get; set; } string CollectionName { get; set; } string DatasetCollectionName { get; set; } + string ModelCollectionName { get; } + string FilesCollectionName { get; set; } } } diff --git a/backend/api/api/Models/Dataset.cs b/backend/api/api/Models/Dataset.cs index 0dc87f40..6cb0b1e9 100644 --- a/backend/api/api/Models/Dataset.cs +++ b/backend/api/api/Models/Dataset.cs @@ -1,46 +1,25 @@ -using MongoDB.Bson; +using System; +using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; namespace api.Models { - public class Dataset - { - internal string uploaderId; + public class Dataset + { + public string username; [BsonId] [BsonRepresentation(BsonType.ObjectId)]//mongo data type to .net public string _id { get; set; } - [BsonElement("uploaderId")] - public string UploaderId { get; set; } - [BsonElement("name")] public string name { get; set; } - public string description { get; set; } - //datetime - public string dateCreated { get; set; } - - public int[] inputColumns { get; set; } - public int columnToPredict { get; set; } - public bool randomTestSet { get; set; } - public int randomTestSetDistribution { get; set; } - - - public string type { get; set; } - public string encoding { get; set; } - public string optimizer { get; set; } - public string lossFunction { get; set; } - public int inputNeurons { get; set; } - public int hiddenLayerNeurons { get; set; } - public int hiddenLayers { get; set; } - public int batchSize { get; set; } - public string inputLayerActivationFunction { get; set; } - public string hiddenLayerActivationFunction { get; set; } - public string outputLayerActivationFunction { get; set; } - - - [BsonElement("extension")] + public string[] header { get; set; } + public int fileId { get; set; } public string extension { get; set; } - - + public bool isPublic { get; set; } + public bool accessibleByLink { get; set; } + public string dateCreated { get; set; } + public string lastUpdated { get; set; } } } + diff --git a/backend/api/api/Models/FileModel.cs b/backend/api/api/Models/FileModel.cs new file mode 100644 index 00000000..9e7c8b5d --- /dev/null +++ b/backend/api/api/Models/FileModel.cs @@ -0,0 +1,14 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace api.Models +{ + public class FileModel + { + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public string _id { get; set; } + public string username { get; set; } + public string path { get; set; } + } +} diff --git a/backend/api/api/Models/JwtToken.cs b/backend/api/api/Models/JwtToken.cs index 3ecbf92d..a98d1967 100644 --- a/backend/api/api/Models/JwtToken.cs +++ b/backend/api/api/Models/JwtToken.cs @@ -23,7 +23,7 @@ namespace api.Models { Subject = new ClaimsIdentity(new[] { new Claim("name", user.UserName), new Claim("role", "User")}), - Expires = DateTime.UtcNow.AddDays(1), + Expires = DateTime.UtcNow.AddMinutes(20), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); @@ -33,13 +33,25 @@ namespace api.Models public string RenewToken(string existingToken) { - if (existingToken == null) + var userName = TokenToUsername(existingToken); + if (userName == null) + return null; + var authUser = new AuthRequest(); + authUser.UserName = userName; + + return GenToken(authUser); + + } + + public string TokenToUsername(string token) + { + if (token == null) return null; var tokenHandler = new JwtSecurityTokenHandler(); - var key= Encoding.ASCII.GetBytes(_configuration.GetSection("AppSettings:JwtToken").Value); + var key = Encoding.ASCII.GetBytes(_configuration.GetSection("AppSettings:JwtToken").Value); try { - tokenHandler.ValidateToken(existingToken, new TokenValidationParameters + tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), @@ -48,11 +60,7 @@ namespace api.Models }, out SecurityToken validatedToken); var jwtToken = (JwtSecurityToken)validatedToken; - var userName =jwtToken.Claims.First(x => x.Type == "name").Value; - var authUser = new AuthRequest(); - authUser.UserName = userName; - - return GenToken(authUser); + return jwtToken.Claims.First(x => x.Type == "name").Value; } catch { diff --git a/backend/api/api/Models/Model.cs b/backend/api/api/Models/Model.cs new file mode 100644 index 00000000..7b22ded8 --- /dev/null +++ b/backend/api/api/Models/Model.cs @@ -0,0 +1,45 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace api.Models +{ + public class Model + { + + [BsonId] + [BsonRepresentation(BsonType.ObjectId)]//mongo data type to .net + public string _id { get; set; } + public string username { get; set; } + + + public string name { get; set; } + public string description { get; set; } + //datetime + public string dateCreated { get; set; } + public string lastUpdated { get; set; } + //proveriti id + public string datasetId { get; set; } + + //Test set settings + public int[] inputColumns { get; set; } + public int columnToPredict { get; set; } + public bool radnomOrder {get;set;} + public bool randomTestSet { get; set; } + public int randomTestSetDistribution { get; set; } + + //Neural net training + public string type { get; set; } + public string encoding { get; set; } + public string optimizer { get; set; } + public string lossFunction { get; set; } + public int inputNeurons { get; set; } + public int hiddenLayerNeurons { get; set; } + public int hiddenLayers { get; set; } + public int batchSize { get; set; } + public string inputLayerActivationFunction { get; set; } + public string hiddenLayerActivationFunction { get; set; } + public string outputLayerActivationFunction { get; set; } + + + } +} diff --git a/backend/api/api/Program.cs b/backend/api/api/Program.cs index 2c569daf..f3287b4c 100644 --- a/backend/api/api/Program.cs +++ b/backend/api/api/Program.cs @@ -28,6 +28,10 @@ builder.Services.AddSingleton<IMongoClient>(s => builder.Services.AddScoped<IDatasetService, DatasetService>(); builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<IAuthService, AuthService>(); +builder.Services.AddScoped<IMlConnectionService, MlConnectionService>(); +builder.Services.AddScoped<IModelService, ModelService>(); +builder.Services.AddScoped<IFileService, FileService>(); + //Add Authentication builder.Services.AddAuthentication( diff --git a/backend/api/api/Services/DatasetService.cs b/backend/api/api/Services/DatasetService.cs index 1b6d22be..80c31758 100644 --- a/backend/api/api/Services/DatasetService.cs +++ b/backend/api/api/Services/DatasetService.cs @@ -21,23 +21,25 @@ namespace api.Services } //brisanje odredjenog name-a - public void Delete(string uploaderId, string name) + public void Delete(string username, string name) { - _dataset.DeleteOne(dataset => (dataset.UploaderId == uploaderId && dataset.name == name)); + _dataset.DeleteOne(dataset => (dataset.username == username && dataset.name == name)); } - public List<Dataset> GetAllDatesets(string uploaderId) + + public List<Dataset> GetAllDatesets(string username) { - return _dataset.Find(dataset => dataset.uploaderId == uploaderId).ToList(); + return _dataset.Find(dataset => dataset.username == username).ToList(); } - public Dataset GetOneDataset(string uploaderId, string name) + + public Dataset GetOneDataset(string username, string name) { - return _dataset.Find(dataset => dataset.UploaderId == uploaderId && dataset.name == name).FirstOrDefault(); + return _dataset.Find(dataset => dataset.username == username && dataset.name == name).FirstOrDefault(); } //ako je potrebno da se zameni name ili ekstenzija - public void Update(string uploaderId, string name, Dataset dataset) + public void Update(string username, string name, Dataset dataset) { - _dataset.ReplaceOne(dataset => dataset.UploaderId == uploaderId && dataset.name == name, dataset); + _dataset.ReplaceOne(dataset => dataset.username == username && dataset.name == name, dataset); } } } diff --git a/backend/api/api/Services/FileService.cs b/backend/api/api/Services/FileService.cs new file mode 100644 index 00000000..e68a0fe3 --- /dev/null +++ b/backend/api/api/Services/FileService.cs @@ -0,0 +1,34 @@ +using api.Interfaces; +using api.Models; +using MongoDB.Driver; + +namespace api.Services +{ + public class FileService : IFileService + { + + private readonly IMongoCollection<FileModel> _file; + + public FileService(IUserStoreDatabaseSettings settings, IMongoClient mongoClient) + { + var database = mongoClient.GetDatabase(settings.DatabaseName); + _file = database.GetCollection<FileModel>(settings.FilesCollectionName); + } + + public FileModel Create(FileModel file) + { + if (file == null) + return null; + _file.InsertOne(file); + return file; + + } + public string GetFilePath(string id, string username) + { + FileModel file = _file.Find(x => x._id == id && x.username == username).FirstOrDefault(); + if (file == null) + return null; + return file.path; + } + } +} diff --git a/backend/api/api/Services/IDatasetService.cs b/backend/api/api/Services/IDatasetService.cs index 9cf8c3cb..49013e29 100644 --- a/backend/api/api/Services/IDatasetService.cs +++ b/backend/api/api/Services/IDatasetService.cs @@ -5,10 +5,10 @@ namespace api.Services { public interface IDatasetService { - Dataset GetOneDataset(string uploaderId, string name); - List<Dataset> GetAllDatesets(string uploaderId); + Dataset GetOneDataset(string username, string name); + List<Dataset> GetAllDatesets(string username); Dataset Create(Dataset dataset); - void Update(string uploaderId, string name, Dataset dataset); - void Delete(string uploaderId, string name); + void Update(string username, string name, Dataset dataset); + void Delete(string username, string name); } } diff --git a/backend/api/api/Services/IFileService.cs b/backend/api/api/Services/IFileService.cs new file mode 100644 index 00000000..7446e283 --- /dev/null +++ b/backend/api/api/Services/IFileService.cs @@ -0,0 +1,10 @@ +using api.Models; + +namespace api.Services +{ + public interface IFileService + { + FileModel Create(FileModel file); + string GetFilePath(string id, string username); + } +}
\ No newline at end of file diff --git a/backend/api/api/Services/IMlConnectionService.cs b/backend/api/api/Services/IMlConnectionService.cs new file mode 100644 index 00000000..f38fb50a --- /dev/null +++ b/backend/api/api/Services/IMlConnectionService.cs @@ -0,0 +1,8 @@ + +namespace api.Services +{ + public interface IMlConnectionService + { + Task<string> SendModelAsync(object model); + } +}
\ No newline at end of file diff --git a/backend/api/api/Services/IModelService.cs b/backend/api/api/Services/IModelService.cs new file mode 100644 index 00000000..149afd4a --- /dev/null +++ b/backend/api/api/Services/IModelService.cs @@ -0,0 +1,15 @@ +using System; +using api.Models; + +namespace api.Services +{ + public interface IModelService + { + Model GetOneModel(string username, string name); + List<Model> GetAllModels(string username); + Model Create(Model model); + void Update(string username, string name, Model model); + void Delete(string username, string name); + } +} + diff --git a/backend/api/api/Services/MlConnectionService.cs b/backend/api/api/Services/MlConnectionService.cs new file mode 100644 index 00000000..7adade0c --- /dev/null +++ b/backend/api/api/Services/MlConnectionService.cs @@ -0,0 +1,17 @@ +using RestSharp; + +namespace api.Services +{ + public class MlConnectionService : IMlConnectionService + { + public async Task<string> SendModelAsync(object model) + { + RestClient client = new RestClient("http://localhost:5000"); + var request = new RestRequest("data", Method.Post); + request.AddJsonBody(model); + var result = await client.ExecuteAsync(request); + return result.Content;//Response od ML microservisa + + } + } +} diff --git a/backend/api/api/Services/ModelService.cs b/backend/api/api/Services/ModelService.cs new file mode 100644 index 00000000..33dea30e --- /dev/null +++ b/backend/api/api/Services/ModelService.cs @@ -0,0 +1,47 @@ +using System; +using api.Interfaces; +using api.Models; +using MongoDB.Driver; + +namespace api.Services +{ + public class ModelService : IModelService + { + + private readonly IMongoCollection<Model> _model; + + public ModelService(IUserStoreDatabaseSettings settings, IMongoClient mongoClient) + { + var database = mongoClient.GetDatabase(settings.DatabaseName); + _model = database.GetCollection<Model>(settings.ModelCollectionName); + } + + public Model Create(Model model) + { + _model.InsertOne(model); + return model; + } + + public void Delete(string username, string name) + { + _model.DeleteOne(model => (model.username == username && model.name == name)); + } + + public List<Model> GetAllModels(string username) + { + return _model.Find(model => model.username == username).ToList(); + } + + public Model GetOneModel(string username, string name) + { + return _model.Find(model => model.username == username && model.name == name).FirstOrDefault(); + } + + public void Update(string username, string name, Model model) + { + _model.ReplaceOne(model => model.username == username && model.name == name, model); + } + + } +} + diff --git a/backend/api/api/api.csproj b/backend/api/api/api.csproj index 46842c3e..f38621ca 100644 --- a/backend/api/api/api.csproj +++ b/backend/api/api/api.csproj @@ -10,6 +10,8 @@ <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.3" /> <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.16.0" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> + <PackageReference Include="RestSharp" Version="107.3.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.16.0" /> </ItemGroup> diff --git a/backend/api/api/appsettings.json b/backend/api/api/appsettings.json index 3661f171..86363075 100644 --- a/backend/api/api/appsettings.json +++ b/backend/api/api/appsettings.json @@ -9,15 +9,19 @@ } }, "AllowedHosts": "*", - "UserStoreDatabaseSettings": { - /* LocalHost + "UserStoreDatabaseSettings": { + /* LocalHost "ConnectionString": "mongodb://127.0.0.1:27017/", "DatabaseName": "si_project", - "CollectionName": "User" + "CollectionName": "User", + "DatasetCollectionName" : "Dataset", + "ModelCollectionName" : "Model" */ - "ConnectionString": "mongodb+srv://si_user:si_user@sidatabase.twtfm.mongodb.net/myFirstDatabase?retryWrites=true&w=majority", - "DatabaseName": "si_db", - "CollectionName": "users", - "DatasetCollectionName" : "Dataset" - } + "ConnectionString": "mongodb+srv://si_user:si_user@sidatabase.twtfm.mongodb.net/myFirstDatabase?retryWrites=true&w=majority", + "DatabaseName": "si_db", + "CollectionName": "users", + "DatasetCollectionName": "Dataset", + "ModelCollectionName": "Model", + "FilesCollectionName": "Files" + } } diff --git a/frontend/angular.json b/frontend/angular.json index bbbe3eaa..b1aaac3f 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -29,9 +29,12 @@ "styles": [ "src/custom-theme.scss", "node_modules/bootstrap/dist/css/bootstrap.min.css", - "src/styles.css" + "src/styles.css", + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css" ], - "scripts": ["node_modules/bootstrap/dist/js/bootstrap.min.js"] + "scripts": [ + "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" + ] }, "configurations": { "production": { @@ -96,6 +99,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.css" ], "scripts": [] @@ -105,4 +109,4 @@ } }, "defaultProject": "frontend" -} +}
\ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ffe47b6a..5b23f3d2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -442,7 +442,6 @@ "version": "13.2.5", "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.2.5.tgz", "integrity": "sha512-Xd8xj2Z0ilA4TJAM/JkTtA1CAa6SuebFsEEvabHCRO5MDvtdsIUP91ADUZIqDHy7qe6Qift/rAVN2PXxT2aaNA==", - "dev": true, "dependencies": { "@babel/core": "^7.17.2", "chokidar": "^3.0.0", @@ -472,7 +471,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", - "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.0" }, @@ -484,7 +482,6 @@ "version": "7.17.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", @@ -514,7 +511,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -523,7 +519,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", - "dev": true, "dependencies": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -537,7 +532,6 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -777,7 +771,6 @@ "version": "7.16.12", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -807,7 +800,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -816,7 +808,6 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -825,7 +816,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, "dependencies": { "@babel/types": "^7.16.8", "jsesc": "^2.5.1", @@ -839,7 +829,6 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3266,7 +3255,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -3540,7 +3528,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } @@ -3637,7 +3624,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -3810,7 +3796,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "funding": [ { "type": "individual", @@ -4736,7 +4721,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", - "dev": true, "engines": { "node": ">= 0.6.0" } @@ -5685,7 +5669,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -5854,7 +5837,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -5964,7 +5946,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -6557,7 +6538,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -6611,7 +6591,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6628,7 +6607,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -6655,7 +6633,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -7511,7 +7488,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7523,7 +7499,6 @@ "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "dev": true, "dependencies": { "sourcemap-codec": "^1.4.4" } @@ -8109,7 +8084,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -8797,7 +8771,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -9593,7 +9566,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -9604,8 +9576,7 @@ "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, "node_modules/regenerate": { "version": "1.4.2", @@ -10026,7 +9997,6 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -10411,8 +10381,7 @@ "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "node_modules/spdy": { "version": "4.0.2", @@ -10813,7 +10782,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -10879,7 +10847,6 @@ "version": "4.5.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11485,8 +11452,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "1.10.2", @@ -11821,7 +11787,6 @@ "version": "13.2.5", "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.2.5.tgz", "integrity": "sha512-Xd8xj2Z0ilA4TJAM/JkTtA1CAa6SuebFsEEvabHCRO5MDvtdsIUP91ADUZIqDHy7qe6Qift/rAVN2PXxT2aaNA==", - "dev": true, "requires": { "@babel/core": "^7.17.2", "chokidar": "^3.0.0", @@ -11839,7 +11804,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", - "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.0" } @@ -11848,7 +11812,6 @@ "version": "7.17.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", - "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", @@ -11870,8 +11833,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -11879,7 +11841,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", - "dev": true, "requires": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -11889,8 +11850,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -12035,7 +11995,6 @@ "version": "7.16.12", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", - "dev": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -12057,14 +12016,12 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -12072,7 +12029,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, "requires": { "@babel/types": "^7.16.8", "jsesc": "^2.5.1", @@ -12082,8 +12038,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -13241,7 +13196,8 @@ "version": "13.2.5", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.2.5.tgz", "integrity": "sha512-obiPvwPe+UJUO8cfNbBxukLKG30F+gLF5/erexwklRknJzS4KP8ciH2on6XlTuXUahpDjbO0pffugFE2I/IszQ==", - "dev": true + "dev": true, + "requires": {} }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -13776,7 +13732,8 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true + "dev": true, + "requires": {} }, "adjust-sourcemap-loader": { "version": "4.0.0", @@ -13899,7 +13856,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -14097,8 +14053,7 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bl": { "version": "4.1.0", @@ -14169,7 +14124,8 @@ "bootstrap": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", - "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==" + "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", + "requires": {} }, "brace-expansion": { "version": "1.1.11", @@ -14184,7 +14140,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -14308,7 +14263,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -14336,7 +14290,8 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz", "integrity": "sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==", - "dev": true + "dev": true, + "requires": {} }, "clean-stack": { "version": "2.2.0", @@ -14814,7 +14769,8 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", - "dev": true + "dev": true, + "requires": {} }, "css-select": { "version": "4.2.1", @@ -14987,8 +14943,7 @@ "dependency-graph": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", - "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", - "dev": true + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==" }, "destroy": { "version": "1.0.4", @@ -15636,7 +15591,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -15759,7 +15713,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "optional": true }, "function-bind": { @@ -15835,7 +15788,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -16092,7 +16044,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -16289,7 +16242,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -16321,8 +16273,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -16333,7 +16284,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -16353,8 +16303,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-path-cwd": { "version": "2.2.0", @@ -16773,7 +16722,8 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz", "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==", - "dev": true + "dev": true, + "requires": {} }, "karma-source-map-support": { "version": "1.4.0", @@ -16990,7 +16940,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -16999,7 +16948,6 @@ "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "dev": true, "requires": { "sourcemap-codec": "^1.4.4" } @@ -17438,8 +17386,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-range": { "version": "0.1.2", @@ -17964,8 +17911,7 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pify": { "version": "2.3.0", @@ -18076,7 +18022,8 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz", "integrity": "sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g==", - "dev": true + "dev": true, + "requires": {} }, "postcss-custom-properties": { "version": "12.1.4", @@ -18146,13 +18093,15 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "dev": true + "dev": true, + "requires": {} }, "postcss-gap-properties": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz", "integrity": "sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-image-set-function": { "version": "4.0.6", @@ -18178,7 +18127,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-lab-function": { "version": "4.1.1", @@ -18205,19 +18155,22 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", - "dev": true + "dev": true, + "requires": {} }, "postcss-media-minmax": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -18261,13 +18214,15 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz", "integrity": "sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-page-break": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-place": { "version": "7.0.4", @@ -18332,7 +18287,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-selector-not": { "version": "5.0.0", @@ -18497,7 +18453,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -18505,8 +18460,7 @@ "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, "regenerate": { "version": "1.4.2", @@ -18782,7 +18736,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "json-schema-traverse": { "version": "0.4.1", @@ -18811,7 +18766,6 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -19127,8 +19081,7 @@ "sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "spdy": { "version": "4.0.2", @@ -19336,7 +19289,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "json-schema-traverse": { "version": "0.4.1", @@ -19410,7 +19364,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -19457,8 +19410,7 @@ "typescript": { "version": "4.5.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", - "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", - "dev": true + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==" }, "ua-parser-js": { "version": "0.7.31", @@ -19648,7 +19600,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "json-schema-traverse": { "version": "0.4.1", @@ -19870,7 +19823,8 @@ "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true + "dev": true, + "requires": {} }, "y18n": { "version": "5.0.8", @@ -19880,8 +19834,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { "version": "1.10.2", diff --git a/frontend/package.json b/frontend/package.json index 0b32837f..01cdf462 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -49,4 +49,4 @@ "karma-jasmine-html-reporter": "~1.7.0", "typescript": "~4.5.2" } -} +}
\ No newline at end of file diff --git a/frontend/src/app/Shared.ts b/frontend/src/app/Shared.ts new file mode 100644 index 00000000..126dc846 --- /dev/null +++ b/frontend/src/app/Shared.ts @@ -0,0 +1,7 @@ +class Shared { + constructor( + public loggedIn: boolean + ) { } +} + +export default new Shared(false);
\ No newline at end of file diff --git a/frontend/src/app/_data/Dataset.ts b/frontend/src/app/_data/Dataset.ts new file mode 100644 index 00000000..aaee50eb --- /dev/null +++ b/frontend/src/app/_data/Dataset.ts @@ -0,0 +1,13 @@ +export default class Dataset { + constructor( + public name: string = 'Novi izvor podataka', + public description: string = '', + public header: string[] = [], + public fileId?: number, + public extension: string = '.csv', + public isPublic: boolean = false, + public accessibleByLink: boolean = false, + public dateCreated: Date = new Date(), + public lastUpdated: Date = new Date() + ) { } +}
\ No newline at end of file diff --git a/frontend/src/app/_data/Model.ts b/frontend/src/app/_data/Model.ts index 216e1c36..c0bc339d 100644 --- a/frontend/src/app/_data/Model.ts +++ b/frontend/src/app/_data/Model.ts @@ -3,11 +3,13 @@ export default class Model { public name: string = 'Novi model', public description: string = '', public dateCreated: Date = new Date(), - public datasetId?: number, + public lastUpdated: Date = new Date(), + public datasetId: string = '', - //Test set settings - public inputColumns: number[] = [0], - public columnToPredict: number = 1, + // Test set settings + public inputColumns: string[] = [], + public columnToPredict: string = '', + public randomOrder: boolean = true, public randomTestSet: boolean = true, public randomTestSetDistribution: number = 0.10, //0.1-0.9 (10% - 90%) @@ -31,6 +33,8 @@ export enum ANNType { Convolutional = 'konvoluciona' } +// replaceMissing srednja vrednost mean, median, najcesca vrednost (mode) +// removeOutliers export enum Encoding { Label = 'label', OneHot = 'one hot' diff --git a/frontend/src/app/_data/Predictor.ts b/frontend/src/app/_data/Predictor.ts new file mode 100644 index 00000000..05b993f1 --- /dev/null +++ b/frontend/src/app/_data/Predictor.ts @@ -0,0 +1,11 @@ +export default class Predictor { + constructor( + public name: string = 'Novi prediktor', + public description: string = '', + public inputs: string[] = [], + public output: string = '', + public isPublic: boolean = false, + public accessibleByLink: boolean = false, + public dateCreated: Date = new Date() + ) { } +}
\ No newline at end of file diff --git a/frontend/src/app/_pages/login-page/login-page.component.css b/frontend/src/app/_elements/carousel/carousel.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/_pages/login-page/login-page.component.css +++ b/frontend/src/app/_elements/carousel/carousel.component.css diff --git a/frontend/src/app/_elements/carousel/carousel.component.html b/frontend/src/app/_elements/carousel/carousel.component.html new file mode 100644 index 00000000..755899a7 --- /dev/null +++ b/frontend/src/app/_elements/carousel/carousel.component.html @@ -0,0 +1,14 @@ +<div class="container"> + <div class="row d-flex align-items-stretch flex-row mx-5 align-items-stretch"> + <div class="col my-1" *ngFor=" let item of items" [ngSwitch]="item.constructor.name"> + <ng-template ngSwitchCase="Dataset"> + <app-item-dataset [dataset]="item"> + </app-item-dataset> + </ng-template> + <ng-template ngSwitchCase="Predictor"> + <app-item-predictor [predictor]="item"> + </app-item-predictor> + </ng-template> + </div> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/login-page/login-page.component.spec.ts b/frontend/src/app/_elements/carousel/carousel.component.spec.ts index 9da3aca8..9196e044 100644 --- a/frontend/src/app/_pages/login-page/login-page.component.spec.ts +++ b/frontend/src/app/_elements/carousel/carousel.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { LoginPageComponent } from './login-page.component'; +import { CarouselComponent } from './carousel.component'; -describe('LoginPageComponent', () => { - let component: LoginPageComponent; - let fixture: ComponentFixture<LoginPageComponent>; +describe('CarouselComponent', () => { + let component: CarouselComponent; + let fixture: ComponentFixture<CarouselComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ LoginPageComponent ] + declarations: [ CarouselComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(LoginPageComponent); + fixture = TestBed.createComponent(CarouselComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_elements/carousel/carousel.component.ts b/frontend/src/app/_elements/carousel/carousel.component.ts new file mode 100644 index 00000000..ed4fa4a5 --- /dev/null +++ b/frontend/src/app/_elements/carousel/carousel.component.ts @@ -0,0 +1,17 @@ +import { Component, Input, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-carousel', + templateUrl: './carousel.component.html', + styleUrls: ['./carousel.component.css'] +}) +export class CarouselComponent { + + @Input() items: any[] = []; + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.css b/frontend/src/app/_elements/dataset-load/dataset-load.component.css index e69de29b..05819702 100644 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.css +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.css @@ -0,0 +1,6 @@ +#divInputs { + margin-left: 20px; +} +#divOutputs { + margin-left: 20px; +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.html b/frontend/src/app/_elements/dataset-load/dataset-load.component.html index c89add43..fcec6ec3 100644 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.html +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.html @@ -1,38 +1,57 @@ <div> - <input style="display: inline-block; width:350px;" list=delimiterOptions - placeholder="Izaberite ili ukucajte delimiter za .csv fajl" class="form-control" [(ngModel)]="delimiter" - (input)="update()"> - <datalist id=delimiterOptions> - <option *ngFor="let option of delimiterOptions">{{option}}</option> - </datalist> - - <label for="checkboxHeader">Da li .csv ima header?</label> - <input (input)="update()" [(ngModel)]="hasHeader" type="checkbox" value="" id="checkboxHeader" checked> - <br><br> + <div class="row mb-4"> + <div class="col-2"> + </div> + <div class="col-3"> + <label for="name" class="col-form-label">Naziv dataseta:</label> + <input type="text" class="form-control mb-3" name="name" placeholder="Naziv..." + [(ngModel)]="dataset.name"> + <label for="desc" class="col-sm-2 col-form-label">Opis:</label> + <div> + <textarea class="form-control" name="desc" rows="3" [(ngModel)]="dataset.description"></textarea> + </div> + </div> + <div class="col-1"> + </div> + <div class="col-4 mt-4"> + <input list=delimiterOptions + placeholder="Izaberite ili ukucajte delimiter za .csv fajl" class="form-control mt-2" [(ngModel)]="delimiter" + (input)="update()"> + <datalist id=delimiterOptions> + <option *ngFor="let option of delimiterOptions">{{option}}</option> + </datalist> - <input id="fileInput" class="form-control mb-5" type="file" class="upload" (change)="changeListener($event)" accept=".csv"> + <label for="type" class="form-check-label my-5">Da li .csv ima header? + <input class="mx-3 form-check-input" type="checkbox" (input)="update()" [(ngModel)]="hasHeader" type="checkbox" value="" id="checkboxHeader" checked> + </label> + <br> + <input id="fileInput" class="form-control" type="file" class="upload" (change)="changeListener($event)" accept=".csv"> + </div> + </div> - <table *ngIf="csvRecords.length > 0 && hasHeader" class="table table-bordered table-light mt-5"> - <thead> - <tr> - <th *ngFor="let item of csvRecords[0]; let i = index">{{item}}</th> - </tr> - </thead> - <tbody> - <tr *ngFor="let row of csvRecords | slice:1:11"> - <td *ngFor="let col of row">{{col}}</td> - </tr> - </tbody> - </table> + <div class="table-responsive"> + <table *ngIf="csvRecords.length > 0 && hasHeader" class="table table-bordered table-light mt-4"> + <thead> + <tr> + <th *ngFor="let item of csvRecords[0]; let i = index">{{item}}</th> + </tr> + </thead> + <tbody> + <tr *ngFor="let row of csvRecords | slice:1:11"> + <td *ngFor="let col of row">{{col}}</td> + </tr> + </tbody> + </table> - <table *ngIf="csvRecords.length > 0 && !hasHeader" class="table table-bordered table-light mt-5"> - <tbody> - <tr *ngFor="let row of csvRecords | slice:0:10"> - <td *ngFor="let col of row">{{col}}</td> - </tr> - </tbody> - </table> + <table *ngIf="csvRecords.length > 0 && !hasHeader" class="table table-bordered table-light mt-4"> + <tbody> + <tr *ngFor="let row of csvRecords | slice:0:10"> + <td *ngFor="let col of row">{{col}}</td> + </tr> + </tbody> + </table> + </div> <div *ngIf="csvRecords.length > 0" id="info"> . . . <br> diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts index 843a5709..8465c3d6 100644 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts @@ -1,5 +1,6 @@ -import { Component, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Output, ViewChild } from '@angular/core'; import { NgxCsvParser, NgxCSVParserError } from 'ngx-csv-parser'; +import Dataset from 'src/app/_data/Dataset'; @Component({ selector: 'app-dataset-load', @@ -8,6 +9,8 @@ import { NgxCsvParser, NgxCSVParserError } from 'ngx-csv-parser'; }) export class DatasetLoadComponent { + @Output() loaded = new EventEmitter<string>(); + delimiter: string = ""; delimiterOptions: Array<string> = [",", ";", "\t", "razmak", "|"]; //podrazumevano "," @@ -20,7 +23,10 @@ export class DatasetLoadComponent { rowsNumber: number = 0; colsNumber: number = 0; + dataset: Dataset; + constructor(private ngxCsvParser: NgxCsvParser) { + this.dataset = new Dataset(); } @ViewChild('fileImportInput', { static: false }) fileImportInput: any; @@ -38,7 +44,7 @@ export class DatasetLoadComponent { this.ngxCsvParser.parse(this.files[0], { header: false, delimiter: (this.delimiter == "razmak") ? " " : (this.delimiter == "") ? "," : this.delimiter}) .pipe().subscribe((result) => { - //console.log('Result', result); + console.log('Result', result); if (result.constructor === Array) { this.csvRecords = result; if (this.hasHeader) @@ -46,9 +52,14 @@ export class DatasetLoadComponent { else this.rowsNumber = this.csvRecords.length; this.colsNumber = this.csvRecords[0].length; + + this.dataset.header = this.csvRecords[0]; + + this.loaded.emit("loaded"); } }, (error: NgxCSVParserError) => { console.log('Error', error); }); } + } diff --git a/frontend/src/app/_pages/only-authorized/only-authorized.component.css b/frontend/src/app/_elements/item-dataset/item-dataset.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/_pages/only-authorized/only-authorized.component.css +++ b/frontend/src/app/_elements/item-dataset/item-dataset.component.css diff --git a/frontend/src/app/_elements/item-dataset/item-dataset.component.html b/frontend/src/app/_elements/item-dataset/item-dataset.component.html new file mode 100644 index 00000000..cf39a125 --- /dev/null +++ b/frontend/src/app/_elements/item-dataset/item-dataset.component.html @@ -0,0 +1,15 @@ +<div class="card" style="min-width: 12rem;"> + <div class="card-header"> + {{dataset.name}} + </div> + <div class="card-body"> + <p class="card-text"> + {{dataset.description}} + </p> + <table class="table table-bordered table-sm"> + <thead> + <th scope="col" *ngFor="let column of dataset.header">{{column}}</th> + </thead> + </table> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-dataset/item-dataset.component.spec.ts b/frontend/src/app/_elements/item-dataset/item-dataset.component.spec.ts new file mode 100644 index 00000000..603889b2 --- /dev/null +++ b/frontend/src/app/_elements/item-dataset/item-dataset.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ItemDatasetComponent } from './item-dataset.component'; + +describe('ItemDatasetComponent', () => { + let component: ItemDatasetComponent; + let fixture: ComponentFixture<ItemDatasetComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ItemDatasetComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemDatasetComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/item-dataset/item-dataset.component.ts b/frontend/src/app/_elements/item-dataset/item-dataset.component.ts new file mode 100644 index 00000000..e12de34d --- /dev/null +++ b/frontend/src/app/_elements/item-dataset/item-dataset.component.ts @@ -0,0 +1,15 @@ +import { Component, Input, OnInit } from '@angular/core'; +import Dataset from 'src/app/_data/Dataset'; + +@Component({ + selector: 'app-item-dataset', + templateUrl: './item-dataset.component.html', + styleUrls: ['./item-dataset.component.css'] +}) +export class ItemDatasetComponent { + + @Input() dataset: Dataset = new Dataset(); + + constructor() { + } +} diff --git a/frontend/src/app/_pages/register-page/register-page.component.css b/frontend/src/app/_elements/item-predictor/item-predictor.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/_pages/register-page/register-page.component.css +++ b/frontend/src/app/_elements/item-predictor/item-predictor.component.css diff --git a/frontend/src/app/_elements/item-predictor/item-predictor.component.html b/frontend/src/app/_elements/item-predictor/item-predictor.component.html new file mode 100644 index 00000000..92d747e2 --- /dev/null +++ b/frontend/src/app/_elements/item-predictor/item-predictor.component.html @@ -0,0 +1,24 @@ +<div class="card" style="min-width: 12rem;"> + <div class="card-header"> + {{predictor.name}} + </div> + <div class="card-body"> + <p class="card-text"> + {{predictor.description}} + </p> + <div class="d-flex flex-column align-items-center"> + <table class="table table-bordered table-sm"> + <thead> + <th class="text-center" *ngFor="let column of predictor.inputs">{{column}}</th> + </thead> + </table> + <mat-icon>arrow_downward</mat-icon> + <p> + {{predictor.output}} + </p> + </div> + </div> + <div class="card-footer text-center"> + <a routerLink="predict" mat-raised-button color="primary">Iskoristi</a> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-predictor/item-predictor.component.spec.ts b/frontend/src/app/_elements/item-predictor/item-predictor.component.spec.ts new file mode 100644 index 00000000..b5c2d91c --- /dev/null +++ b/frontend/src/app/_elements/item-predictor/item-predictor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ItemPredictorComponent } from './item-predictor.component'; + +describe('ItemPredictorComponent', () => { + let component: ItemPredictorComponent; + let fixture: ComponentFixture<ItemPredictorComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ItemPredictorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemPredictorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/item-predictor/item-predictor.component.ts b/frontend/src/app/_elements/item-predictor/item-predictor.component.ts new file mode 100644 index 00000000..cc782f45 --- /dev/null +++ b/frontend/src/app/_elements/item-predictor/item-predictor.component.ts @@ -0,0 +1,18 @@ +import { Component, Input, OnInit } from '@angular/core'; +import Predictor from 'src/app/_data/Predictor'; + +@Component({ + selector: 'app-item-predictor', + templateUrl: './item-predictor.component.html', + styleUrls: ['./item-predictor.component.css'] +}) +export class ItemPredictorComponent implements OnInit { + + @Input() predictor: Predictor = new Predictor(); + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_elements/navbar/navbar.component.css b/frontend/src/app/_elements/navbar/navbar.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_elements/navbar/navbar.component.css diff --git a/frontend/src/app/_elements/navbar/navbar.component.html b/frontend/src/app/_elements/navbar/navbar.component.html new file mode 100644 index 00000000..b9c450af --- /dev/null +++ b/frontend/src/app/_elements/navbar/navbar.component.html @@ -0,0 +1,49 @@ +<header class="sticky-top p-3 bg-dark text-white"> + <div class="container"> + <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start"> + <a routerLink="" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none"> + <img src="../../../assets/svg/logo_no_text.svg" class="bi me-2" width="64" height="40"> + </a> + + <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0"> + <li><a routerLink="" class="nav-link px-2" + [class]="(currentUrl === '') ? 'text-secondary' : 'text-white'">Početna</a></li> + <li><a routerLink="add-model" class="nav-link px-2" + [class]="(currentUrl === '/add-model') ? 'text-secondary' : 'text-white'">Dodaj model</a></li> + <li><a routerLink="my-predictors" class="nav-link px-2" + [class]="(currentUrl === '/my-predictors') ? 'text-secondary' : 'text-white'">Predvidi</a></li> + </ul> + + <div *ngIf="shared.loggedIn" class="dropdown text-end"> + <a href="#" class="d-block link-light text-decoration-none dropdown-toggle" id="dropdownUser1" + data-bs-toggle="dropdown" aria-expanded="false"> + <img src="https://github.com/mdo.png" alt="mdo" width="32" height="32" class="rounded-circle"> + </a> + <ul class="dropdown-menu text-small" aria-labelledby="dropdownUser1" + style="position: absolute; inset: 0px 0px auto auto; margin: 0px; transform: translate(0px, 34px);" + data-popper-placement="bottom-end"> + <li><a class="dropdown-item" routerLink="add-model">Nov model...</a></li> + <li><a class="dropdown-item" routerLink="settings">Podešavanja</a></li> + <li><a class="dropdown-item" routerLink="profile">Moj profil</a></li> + <li> + <hr class="dropdown-divider"> + </li> + <li><a class="dropdown-item" routerLink="" (click)="logOut()">Odjavi se</a></li> + </ul> + </div> + <div *ngIf="!shared.loggedIn" class="dropdown text-end"> + <button type="button" mat-raised-button color="primary" class="mx-2" data-bs-toggle="modal" + data-bs-target="#modalForLogin"> + Prijavi se + </button> + <button type="button" mat-raised-button color="primary" data-bs-toggle="modal" + data-bs-target="#modalForRegister"> + Registruj se + </button> + </div> + </div> + </div> +</header> + +<app-login-modal></app-login-modal> +<app-register-modal></app-register-modal>
\ No newline at end of file diff --git a/frontend/src/app/_elements/navbar/navbar.component.spec.ts b/frontend/src/app/_elements/navbar/navbar.component.spec.ts new file mode 100644 index 00000000..f8ccd6f4 --- /dev/null +++ b/frontend/src/app/_elements/navbar/navbar.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NavbarComponent } from './navbar.component'; + +describe('NavbarComponent', () => { + let component: NavbarComponent; + let fixture: ComponentFixture<NavbarComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NavbarComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NavbarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/navbar/navbar.component.ts b/frontend/src/app/_elements/navbar/navbar.component.ts new file mode 100644 index 00000000..c16e3e9d --- /dev/null +++ b/frontend/src/app/_elements/navbar/navbar.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit } from '@angular/core'; +import { Location } from '@angular/common'; +import { AuthService } from '../../_services/auth.service'; +import shared from 'src/app/Shared'; + +@Component({ + selector: 'app-navbar', + templateUrl: './navbar.component.html', + styleUrls: ['./navbar.component.css'] +}) +export class NavbarComponent implements OnInit { + + currentUrl: string; + shared = shared; + + constructor(public location: Location, private auth: AuthService) { + this.currentUrl = this.location.path(); + this.location.onUrlChange(() => { + this.currentUrl = this.location.path(); + }) + } + + ngOnInit(): void { + this.auth.updateUser(); + } + + logOut() { + this.auth.logOut(); + } +} diff --git a/frontend/src/app/_modals/login-modal/login-modal.component.html b/frontend/src/app/_modals/login-modal/login-modal.component.html index 22f50de2..d694ea58 100644 --- a/frontend/src/app/_modals/login-modal/login-modal.component.html +++ b/frontend/src/app/_modals/login-modal/login-modal.component.html @@ -1,14 +1,9 @@ -<!-- Button trigger modal, OVO JE U STVARI DUGME U NAVBARU --> -<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalForLogin" (click)="openModal()"> - Otvori login modal -</button> - <!-- Modal --> <div class="modal fade" id="modalForLogin" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered"> <div class="modal-content"> - <div class="modal-header bg-info"> - <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + <div class="modal-header" style="background-color: #003459;"> + <button id="closeButton" type="button" class="btn-close" style="background-color:white;" data-bs-dismiss="modal" aria-label="Close" (click)="resetData()"></button> </div> <div class="modal-body px-5" style="color:#003459"> <h1 class="text-center mt-2 mb-4">Prijavite se</h1> @@ -32,18 +27,16 @@ </div> </form> <div class="col-md-12 d-flex justify-content-center"> - <button type="button" class="btn btn-lg btn-info" style="color:white; border-color: #00a8e8; margin-right: 10px;" (click)="doLogin()">Prijavite se</button> - <button type="button" class="btn btn-lg btn-outline-secondary" data-bs-dismiss="modal">Odustanite</button> + <button type="button" class="btn btn-lg" style="color:white; background-color: #003459; margin-right: 10px;" (click)="doLogin()">Prijavite se</button> + <button type="button" class="btn btn-lg btn-outline-secondary" data-bs-dismiss="modal" (click)="resetData()">Odustanite</button> </div> <br> </div> <div class="modal-footer justify-content-center"> <p class="small fw-bold">Još uvek nemate nalog? - <a routerLink="/register" class="link-danger">Registrujte se</a> + <a data-bs-toggle="modal" data-bs-target="#modalForRegister" class="link-danger">Registrujte se</a> </p> </div> </div> </div> -</div> - - +</div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/login-modal/login-modal.component.ts b/frontend/src/app/_modals/login-modal/login-modal.component.ts index 3a6fd8f1..d17d7017 100644 --- a/frontend/src/app/_modals/login-modal/login-modal.component.ts +++ b/frontend/src/app/_modals/login-modal/login-modal.component.ts @@ -1,11 +1,7 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import { CookieService } from 'ngx-cookie-service'; import { AuthService } from 'src/app/_services/auth.service'; -import { ElementRef } from '@angular/core'; - -declare var window: any; @Component({ selector: 'app-login-modal', @@ -14,7 +10,6 @@ declare var window: any; }) export class LoginModalComponent implements OnInit { - loginModal: any; username: string = ''; password: string = ''; @@ -27,25 +22,22 @@ export class LoginModalComponent implements OnInit { ) { } ngOnInit(): void { - this.loginModal = new window.bootstrap.Modal( - document.getElementById("modalForLogin") - ); } - openModal() { - this.loginModal.show(); - //console.log("ok"); - //(<HTMLInputElement>document.getElementById("exampleModal")).style.display = "block"; - } doLogin() { - this.authService.login(this.username, this.password).subscribe((response) => { //ako nisu ok podaci, ne ide hide nego mora opet da ukucava!!!!podesi - console.log(response); - this.cookie.set('token', response); - this.loginModal.hide(); //dodato - this.router.navigate(['add-model']); - }); - } - sendToRegister() { + if (this.username.length > 0 && this.password.length > 0) { + this.authService.login(this.username, this.password).subscribe((response) => { //ako nisu ok podaci, ne ide hide nego mora opet da ukucava!!!!podesi + console.log(response); + this.authService.authenticate(response); + (<HTMLSelectElement>document.getElementById('closeButton')).click(); + }, error => { + console.warn(error); //NETACNI PODACI + }); + } } + resetData() { + this.username = ''; + this.password = ''; + } } diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.css b/frontend/src/app/_modals/register-modal/register-modal.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_modals/register-modal/register-modal.component.css diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.html b/frontend/src/app/_modals/register-modal/register-modal.component.html new file mode 100644 index 00000000..7098c040 --- /dev/null +++ b/frontend/src/app/_modals/register-modal/register-modal.component.html @@ -0,0 +1,88 @@ +<!-- Modal --> +<div class="modal fade" id="modalForRegister" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" + aria-labelledby="staticBackdropLabel" aria-hidden="true"> + <div class="modal-dialog modal-dialog-centered modal-dialog modal-lg"> + <div class="modal-content"> + <div class="modal-header" style="background-color: #003459;"> + <button type="button" class="btn-close" data-bs-dismiss="modal" style="background-color: white;" + aria-label="Close" (click)="resetData()"></button> + </div> + <div class="modal-body" style="color:#003459"> + <h1 class="text-center mt-2 mb-4">Registracija</h1> + + <form class="mx-5"> + <!--Ime--> + <div class="row"> + <div class="col-6 px-3 py-3"> + <label class="form-label" for="firstName">Ime</label> + <input type="text" id="firstName" class="form-control" [(ngModel)]="firstName" + name="firstName" placeholder="Unesite ime..."> + <small *ngIf="wrongFirstNameBool" class="form-text text-danger">Unesite ispravno + ime.</small> + </div> + <!--Prezime--> + <div class="col-6 px-3 py-3"> + <label class="form-label" for="lastName">Prezime</label> + <input type="text" id="lastName" class="form-control" [(ngModel)]="lastName" name="lastName" + placeholder="Unesite prezime..." /> + <small *ngIf="wrongLastNameBool" class="form-text text-danger">Unesite ispravno + prezime.</small> + </div> + </div> + <div class="row"> + <!--Korisnicko ime--> + <div class="col-12 px-3 py-3"> + <label class="form-label" for="username-register">Korisničko ime</label> + <input type="text" id="username-register" class="form-control" [(ngModel)]="username" + name="username-register" placeholder="Unesite korisničko ime..." /> + <small *ngIf="wrongUsernameBool" class="form-text text-danger">Unesite ispravno korisničko + ime.</small> + </div> + </div> + <div class="row"> + <!--Email--> + <div class="col-12 px-3 py-3"> + <label class="form-label" for="email">E-mail adresa</label> + <input type="email" id="email" class="form-control" [(ngModel)]="email" name="email" + placeholder="Unesite email adresu..." /> + <small *ngIf="wrongEmailBool" class="form-text text-danger">Unesite ispravno e-mail + adresu.</small> + </div> + </div> + <div class="row"> + <!-- Lozinka 1. --> + <div class="col-6 px-3 py-3"> + <label class="form-label" for="pass1">Lozinka</label> + <input type="password" id="pass1" class="form-control" [(ngModel)]="pass1" name="pass1" + placeholder="Unesite lozinku..." /> + <small *ngIf="wrongPass1Bool" class="form-text text-danger">Lozinka se mora sastojati od + najmanje 6 karaktera.</small> + </div> + <!-- Lozinka 2. --> + <div class="col-6 px-3 py-3"> + <label class="form-label" for="pass2">Potvrdite lozinku</label> + <input type="password" id="pass2" class="form-control" [(ngModel)]="pass2" name="pass2" + placeholder="Ponovite lozinku..." /> + <small *ngIf="wrongPass2Bool" class="form-text text-danger">Lozinke se ne + podudaraju.</small> + </div> + </div> + </form> + <div class="col-md-12 d-flex justify-content-center mt-5"> + <button type="button" class="btn btn-lg" + style="color:white; background-color: #003459; margin-right: 10px;" + (click)="doRegister()">Registrujte se</button> + <button type="button" class="btn btn-lg btn-outline-secondary" style="margin-left: 15px;" + data-bs-dismiss="modal" (click)="resetData()">Odustanite</button> + </div> + <br> + </div> + <div class="modal-footer justify-content-center"> + <p class="small fw-bold">Već imate kreiran nalog? + <a id="linkToLoginModal" data-bs-toggle="modal" data-bs-target="#modalForLogin" + class="link-danger">Prijavite se</a> + </p> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.spec.ts b/frontend/src/app/_modals/register-modal/register-modal.component.spec.ts new file mode 100644 index 00000000..e371b3d8 --- /dev/null +++ b/frontend/src/app/_modals/register-modal/register-modal.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RegisterModalComponent } from './register-modal.component'; + +describe('RegisterModalComponent', () => { + let component: RegisterModalComponent; + let fixture: ComponentFixture<RegisterModalComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ RegisterModalComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RegisterModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/register-page/register-page.component.ts b/frontend/src/app/_modals/register-modal/register-modal.component.ts index 712fc55e..c02a4e1a 100644 --- a/frontend/src/app/_pages/register-page/register-page.component.ts +++ b/frontend/src/app/_modals/register-modal/register-modal.component.ts @@ -1,45 +1,58 @@ import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; import { AuthService } from 'src/app/_services/auth.service'; @Component({ - selector: 'app-register-page', - templateUrl: './register-page.component.html', - styleUrls: ['./register-page.component.css'] + selector: 'app-register-modal', + templateUrl: './register-modal.component.html', + styleUrls: ['./register-modal.component.css'] }) -export class RegisterPageComponent implements OnInit { +export class RegisterModalComponent implements OnInit { + firstName: string = ''; lastName: string = ''; - nickName: string = ''; + username: string = ''; email: string = ''; pass1: string = ''; pass2: string = ''; wrongFirstNameBool: boolean = false; wrongLastNameBool: boolean = false; - wrongNickNameBool: boolean = false; + wrongUsernameBool: boolean = false; wrongEmailBool: boolean = false; wrongPass1Bool: boolean = false; wrongPass2Bool: boolean = false; pattName: RegExp = /^[a-zA-ZšŠđĐčČćĆžŽ]+([ \-][a-zA-ZšŠđĐčČćĆžŽ]+)*$/; + pattUsername: RegExp = /^[a-zA-Z0-9]{6,18}$/; pattTwoSpaces: RegExp = / /; pattEmail: RegExp = /^[a-zA-Z0-9]+([\.\-\+][a-zA-Z0-9]+)*\@([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}$/; pattPassword: RegExp = /.{6,30}$/; constructor( - private router: Router, - private authService: AuthService, + private authService: AuthService ) { } ngOnInit(): void { } + doRegister() { + this.validation(); + } + resetData() { + this.firstName = this.lastName = this.username = this.email = this.pass1 = this.pass2 = ''; + this.wrongFirstNameBool = this.wrongLastNameBool = this.wrongUsernameBool = this.wrongEmailBool = this.wrongPass1Bool = this.wrongPass2Bool = false; + } + isCorrectName(element: string): boolean { if (this.pattName.test(element) && !(this.pattTwoSpaces.test(element)) && (element.length >= 1 && element.length <= 30)) return true; return false; } + isCorrectUsername(element: string): boolean { + if (this.pattUsername.test(element) && !(this.pattTwoSpaces.test(element)) && (element.length >= 1 && element.length <= 30)) + return true; + return false; + } isCorrectEmail(element: string): boolean { if (this.pattEmail.test(element.toLowerCase()) && element.length <= 320) return true; @@ -67,13 +80,13 @@ export class RegisterPageComponent implements OnInit { (<HTMLSelectElement>document.getElementById('lastName')).focus(); this.wrongLastNameBool = true; } - nickNameValidation() { - if (this.isCorrectName(this.nickName) == true) { - this.wrongNickNameBool = false; + usernameValidation() { + if (this.isCorrectUsername(this.username) == true) { + this.wrongUsernameBool = false; return; } - (<HTMLSelectElement>document.getElementById('nickName')).focus(); - this.wrongNickNameBool = true; + (<HTMLSelectElement>document.getElementById('username-register')).focus(); + this.wrongUsernameBool = true; } emailValidation() { if (this.isCorrectEmail(this.email) == true) { @@ -99,22 +112,22 @@ export class RegisterPageComponent implements OnInit { validation() { this.firstName = this.firstName.trim(); this.lastName = this.lastName.trim(); - this.nickName = this.nickName.trim(); + this.username = this.username.trim(); this.email = this.email.trim(); this.firstNameValidation(); this.lastNameValidation(); - //this.nickNameValidation(); + this.usernameValidation(); this.emailValidation(); this.passwordValidation(); - if (!(this.wrongFirstNameBool || this.wrongLastNameBool || this.wrongNickNameBool || + if (!(this.wrongFirstNameBool || this.wrongLastNameBool || this.wrongUsernameBool || this.wrongEmailBool || this.wrongPass1Bool || this.wrongPass2Bool)) { //sve ok, registruj ga let user = { firstName: this.firstName, lastName: this.lastName, - username: this.nickName, + username: this.username, password: this.pass1, email: this.email } @@ -123,18 +136,22 @@ export class RegisterPageComponent implements OnInit { .subscribe( (response) => { console.log(response); - if (response === 'User added') - this.router.navigate(['/login']); //registracija uspesna, idi na login - else if (response === 'Email Already Exists') + if (response === 'User added') { + this.resetData(); + (<HTMLSelectElement>document.getElementById('linkToLoginModal')).click(); + } + else if (response === 'Email Already Exists') { alert('Nalog sa unetim email-om već postoji!'); - else if (response === 'Username Already Exists') - alert('Nalog sa unetim korisnićkim imenom već postoji!'); + (<HTMLSelectElement>document.getElementById('email')).focus(); + } + else if (response === 'Username Already Exists') { + alert('Nalog sa unetim korisničkim imenom već postoji!'); + (<HTMLSelectElement>document.getElementById('username-register')).focus(); + } } ); } } - - } diff --git a/frontend/src/app/_pages/add-model/add-model.component.css b/frontend/src/app/_pages/add-model/add-model.component.css index e69de29b..4bf569cc 100644 --- a/frontend/src/app/_pages/add-model/add-model.component.css +++ b/frontend/src/app/_pages/add-model/add-model.component.css @@ -0,0 +1,18 @@ +#header { + background-color: #003459; + padding-top: 30px; + padding-bottom: 20px; +} +#header h1 { + font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; + text-align: center; + color: white; +} + +#container { + border-radius: 8px; +} + +#wrapper { + color: #003459; +}
\ No newline at end of file diff --git a/frontend/src/app/_pages/add-model/add-model.component.html b/frontend/src/app/_pages/add-model/add-model.component.html index bc292bb9..004f308b 100644 --- a/frontend/src/app/_pages/add-model/add-model.component.html +++ b/frontend/src/app/_pages/add-model/add-model.component.html @@ -1,189 +1,257 @@ -<div class="container p-3" style="background-color: rgb(249, 251, 253); min-height: 100%;"> +<div id="header"> + <h1>Napravite svoj model veštačke neuronske mreže</h1> +</div> - <h2 class="my-4 text-primary"> Nov model: </h2> - <div class="form-group row align-items-center"> - <label for="name" class="col-sm-2 col-form-label col-form-label-lg">Naziv</label> - <div class="col-sm-7"> - <input type="text" class="form-control form-control-lg" name="name" placeholder="Naziv..." - [(ngModel)]="newModel.name"> +<div id="wrapper"> + + <div id="container" class="container p-5" style="background-color: white; min-height: 100%;"> + + <div class="form-group row mb-4 d-flex justify-content-center"> <!--justify-content-center--> + <h2 class="col-2"> Nov model: </h2> + <div class="col-3"> + <label for="name" class="col-form-label">Naziv modela:</label> + <input type="text" class="form-control" name="name" placeholder="Naziv..." + [(ngModel)]="newModel.name"> + </div> + <div class="col-5"> + <label for="desc" class="col-sm-2 col-form-label">Opis:</label> + <div> + <textarea class="form-control" name="desc" rows="3" [(ngModel)]="newModel.description"></textarea> + </div> + </div> + <div class="col-2"> + <label for="dateCreated" class="col-form-label">Datum:</label> + <input type="text" class="form-control-plaintext" id="dateCreated" placeholder="--/--/--" + value="{{newModel.dateCreated | date: 'dd/MM/yyyy'}}" readonly> + </div> </div> - - <div class="col-sm-3"> - <input type="text" class="form-control-plaintext text-center" id="dateCreated" placeholder="--/--/--" - value="{{newModel.dateCreated | date: 'dd/MM/yyyy'}}" readonly> + + <div class="mt-5 justify-content-center"> + <h2>Izvor podataka:</h2> + <app-dataset-load id="dataset" (loaded)="datasetLoaded = true"></app-dataset-load> </div> - </div> - <div class="form-group row my-2"> - <label for="desc" class="col-sm-2 col-form-label">Opis</label> - <div class="col-sm-10"> - <textarea class="form-control" name="desc" rows="3" [(ngModel)]="newModel.description"></textarea> + <div *ngIf="datasetLoaded"> + <div *ngIf="datasetLoadComponent" class="row"> + <div class="col d-flex justify-content-center"> + <h3>Izaberite ulazne kolone:</h3> + <div id="divInputs" class="form-check mt-2"> + <br> + <div *ngFor="let item of datasetLoadComponent.dataset.header; let i = index"> + <input *ngIf="i == 0" class="form-check-input" type="checkbox" value="{{item}}" id="cb_{{item}}" name="cbs" checked> + <input *ngIf="i != 0" class="form-check-input" type="checkbox" value="{{item}}" id="cb_{{item}}" name="cbs"> + <label class="form-check-label" for="cb_{{item}}"> + {{item}} + </label> + </div> + </div> + </div> + <div class="col d-flex justify-content-left"> + <h3>Izaberite izlaznu kolonu:</h3> + <div id="divOutputs" class="form-check mt-2"> + <br> + <div *ngFor="let item of datasetLoadComponent.dataset.header; let i = index"> + <input *ngIf="i == 0" class="form-check-input" type="radio" value="{{item}}" id="rb_{{item}}" name="rbs" checked> + <input *ngIf="i != 0" class="form-check-input" type="radio" value="{{item}}" id="rb_{{item}}" name="rbs"> + <label class="form-check-label" for="rb_{{item}}"> + {{item}} + </label> + </div> + </div> + </div> + </div> </div> - </div> - <!--<div class="form-group row"> - <label for="value" class="col-4">Vrednost</label> - <div class="input-group"> - <input type="number" min="0" class="form-control" name="value" placeholder="Vrednost..." - [(ngModel)]="newModel.value"> - <div class="input-group-prepend"> - <span class="input-group-text">#</span> - </div> - <input type="number" min="1" class="form-control" name="count" placeholder="Br." [(ngModel)]="newModel.count"> - <input type="text" class="form-control" name="sum" placeholder="Suma" - value="=({{newModel.value * newModel.count}})" readonly> - </div> - </div>--> + + + <h2 class="mt-5 mb-4">Parametri treniranja:</h2> - <div class="my-4"> - <label for="dataset">Izvor podataka:</label> - <app-dataset-load id="dataset"></app-dataset-load> - </div> - - <div class="form-group row my-2"> - <div class="col-sm-2 col-form-label"> - <label for="type" class="form-check-label">Podela test skupa: - <input class="mx-3 form-check-input" type="checkbox" [checked]="newModel.randomTestSet" - (change)="newModel.randomTestSet = !newModel.randomTestSet"> - </label> - - </div> <div> - <input type="range" min="0.1" max="0.9" step="0.1" class="form-control" name="randomTestSetDistribution" - [disabled]="!newModel.randomTestSet" [(ngModel)]="newModel.randomTestSetDistribution"> - </div> - </div> - - <h3> Parametri treniranja </h3> - - <div class="form-group row my-2"> - <label for="type" class="col-sm-2 col-form-label">Tip mreže: </label> - <div class="col-sm-10"> - <select id=typeOptions class="form-control" name="type" [(ngModel)]="newModel.type"> - <option *ngFor="let option of Object.keys(ANNType); let optionName of Object.values(ANNType)" - [value]="option"> - {{ optionName }} - </option> - </select> - </div> - </div> - - <div class="form-group row my-2"> - <label for="encoding" class="col-sm-2 col-form-label">Enkoding: </label> - <div class="col-sm-10"> - <select id=encodingOptions class="form-control" name="encoding" [(ngModel)]="newModel.encoding"> - <option *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" - [value]="option"> - {{ optionName }} - </option> - </select> - </div> - </div> + <div class="row p-2"> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="type" class="col-form-label">Tip mreže: </label> + </div> + <div class="col-2"> + <select id=typeOptions class="form-control" name="type" [(ngModel)]="newModel.type"> + <option *ngFor="let option of Object.keys(ANNType); let optionName of Object.values(ANNType)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="hiddenLayers" class="col-form-label">Broj skrivenih slojeva: </label> + </div> + <div class="col-1"> + <input type="number" min="1" class="form-control" name="hiddenLayers" [(ngModel)]="newModel.hiddenLayers"> + </div> + </div> - <div class="form-group row my-2"> - <label for="optimizer" class="col-sm-2 col-form-label">Optimizacija: </label> - <div class="col-sm-10"> - <select id=optimizerOptions class="form-control" name="optimizer" [(ngModel)]="newModel.optimizer"> - <option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" - [value]="option"> - {{ optionName }} - </option> - </select> - </div> - </div> + <div class="row p-2"> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="encoding" class="col-form-label">Enkoding: </label> + </div> + <div class="col-2"> + <select id=encodingOptions class="form-control" name="encoding" [(ngModel)]="newModel.encoding"> + <option *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="hiddenLayerNeurons" class="col-form-label">Broj neurona skrivenih slojeva: </label> + </div> + <div class="col-1"> + <input type="number" min="1" class="form-control" name="hiddenLayerNeurons" [(ngModel)]="newModel.hiddenLayerNeurons"> + </div> + </div> - <div class="form-group row my-2"> - <label for="lossFunction" class="col-sm-2 col-form-label">Funkcija obrade gubitka: </label> - <div class="col-sm-10"> - <select id=lossFunctionOptions class="form-control" name="lossFunction" [(ngModel)]="newModel.lossFunction"> - <option *ngFor="let option of Object.keys(LossFunction); let optionName of Object.values(LossFunction)" - [value]="option"> - {{ optionName }} - </option> - </select> - </div> - </div> + <div class="row p-2"> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="optimizer" class="col-form-label">Optimizacija: </label> + </div> + <div class="col-2"> + <select id=optimizerOptions class="form-control" name="optimizer" [(ngModel)]="newModel.optimizer"> + <option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="batchSize" class="col-form-label">Broj uzorka po iteraciji: </label> + </div> + <div class="col-1"> + <input type="number" min="1" class="form-control" name="batchSize" [(ngModel)]="newModel.batchSize"> + </div> + </div> - <div class="form-group row my-2"> - <label for="inputNeurons" class="col-sm-2 col-form-label">Broj ulaznih neurona: </label> - <div class="col-sm-10"> - <input type="number" min="1" class="form-control" name="inputNeurons" [(ngModel)]="newModel.inputNeurons"> - </div> - </div> + <div class="row p-2"> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="lossFunction" class="col-form-label">Funkcija obrade gubitka: </label> + </div> + <div class="col-2"> + <select id=lossFunctionOptions class="form-control" name="lossFunction" [(ngModel)]="newModel.lossFunction"> + <option *ngFor="let option of Object.keys(LossFunction); let optionName of Object.values(LossFunction)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"> + </div> + <div class="col-3 mt-2"> + <label for="type" class="form-check-label">Nasumičan redosled podataka?</label> + <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="newModel.randomOrder" type="checkbox" value="" checked> + </div> + <div class="col-1"> + </div> + </div> - <div class="form-group row my-2"> - <label for="inputLayerActivationFunction" class="col-sm-2 col-form-label">Funkcija aktivacije ulaznog sloja: - </label> - <div class="col-sm-10"> - <select id=inputLayerActivationFunctionOptions class="form-control" name="inputLayerActivationFunction" - [(ngModel)]="newModel.inputLayerActivationFunction"> - <option - *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" - [value]="option"> - {{ optionName }} - </option> - </select> - </div> - </div> + <div class="row p-2"> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="inputLayerActivationFunction" class="col-form-label">Funkcija aktivacije ulaznog sloja:</label> + </div> + <div class="col-2"> + <select id=inputLayerActivationFunctionOptions class="form-control" name="inputLayerActivationFunction" + [(ngModel)]="newModel.inputLayerActivationFunction"> + <option + *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"> + </div> + <div class="col-5"> + <label for="type" class="form-check-label">Podela test skupa: + <input class="form-check-input" type="checkbox" [checked]="newModel.randomTestSet" + (change)="newModel.randomTestSet = !newModel.randomTestSet"> + </label> + test + <mat-slider min="0.1" max="0.9" step="0.1" value="0.2" name="randomTestSetDistribution" thumbLabel + [disabled]="!newModel.randomTestSet" [(ngModel)]="newModel.randomTestSetDistribution"> + </mat-slider> + trening + </div> + <div class="col"> + </div> + </div> - <div class="form-group row my-2"> - <label for="hiddenLayerNeurons" class="col-sm-2 col-form-label">Broj neurona skrivenih slojeva: </label> - <div class="col-sm-10"> - <input type="number" min="1" class="form-control" name="hiddenLayerNeurons" - [(ngModel)]="newModel.hiddenLayerNeurons"> - </div> - </div> + <div class="row p-2"> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="hiddenLayerActivationFunction" class="col-form-label">Funkcija aktivacije skrivenih slojeva:</label> + </div> + <div class="col-2"> + <select id=hiddenLayerActivationFunctionOptions class="form-control" name="hiddenLayerActivationFunction" + [(ngModel)]="newModel.hiddenLayerActivationFunction"> + <option + *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col"> + </div> + <div class="col"> + </div> + </div> - <div class="form-group row my-2"> - <label for="hiddenLayerActivationFunction" class="col-sm-2 col-form-label">Funkcija aktivacije skrivenih - slojeva: - </label> - <div class="col-sm-10"> - <select id=hiddenLayerActivationFunctionOptions class="form-control" name="hiddenLayerActivationFunction" - [(ngModel)]="newModel.hiddenLayerActivationFunction"> - <option - *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" - [value]="option"> - {{ optionName }} - </option> - </select> + <div class="row p-2"> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="outputLayerActivationFunction" class="col-form-label">Funkcija aktivacije izlaznog sloja:</label> + </div> + <div class="col-2"> + <select id=outputLayerActivationFunctionOptions class="form-control" name="outputLayerActivationFunction" + [(ngModel)]="newModel.outputLayerActivationFunction"> + <option + *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col"> + </div> + <div class="col"> + </div> + </div> </div> - </div> - <div class="form-group row my-2"> - <label for="hiddenLayers" class="col-sm-2 col-form-label">Broj skrivenih slojeva: </label> - <div class="col-sm-10"> - <input type="number" min="1" class="form-control" name="hiddenLayers" [(ngModel)]="newModel.hiddenLayers"> - </div> - </div> - <div class="form-group row my-2"> - <label for="outputLayerActivationFunction" class="col-sm-2 col-form-label">Funkcija aktivacije izlaznog - sloja: - </label> - <div class="col-sm-10"> - <select id=outputLayerActivationFunctionOptions class="form-control" name="outputLayerActivationFunction" - [(ngModel)]="newModel.outputLayerActivationFunction"> - <option - *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" - [value]="option"> - {{ optionName }} - </option> - </select> + <br><br> + <div class="form-group row mt-5 mb-3"> + <div class="col"></div> + <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="addModel();">Sačuvaj model</button> + <div class="col"></div> + <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="trainModel();">Treniraj model</button> + <div class="col"></div> </div> - </div> - <div class="form-group row my-2"> - <label for="batchSize" class="col-sm-2 col-form-label">Broj uzorka po iteraciji: </label> - <div class="col-sm-10"> - <input type="number" min="1" class="form-control" name="batchSize" [(ngModel)]="newModel.batchSize"> - </div> </div> +</div> - <div class=" form-group row my-4"> - <div class="col-4"></div> - <button class="btn btn-lg btn-primary col-4" (click)="addModel();">Dodaj</button> - <div class="col-4"></div> - </div> -</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/add-model/add-model.component.ts b/frontend/src/app/_pages/add-model/add-model.component.ts index 3cb47d61..01e8ade9 100644 --- a/frontend/src/app/_pages/add-model/add-model.component.ts +++ b/frontend/src/app/_pages/add-model/add-model.component.ts @@ -1,6 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { Observable, of } from 'rxjs'; import Model from 'src/app/_data/Model'; import { ANNType, Encoding, ActivationFunction, LossFunction, Optimizer } from 'src/app/_data/Model'; +import { DatasetLoadComponent } from 'src/app/_elements/dataset-load/dataset-load.component'; +import { ModelsService } from 'src/app/_services/models.service'; + @Component({ selector: 'app-add-model', @@ -9,7 +13,10 @@ import { ANNType, Encoding, ActivationFunction, LossFunction, Optimizer } from ' }) export class AddModelComponent implements OnInit { - newModel: Model + @ViewChild(DatasetLoadComponent) datasetLoadComponent?: DatasetLoadComponent; + datasetLoaded: boolean = false; + + newModel: Model; ANNType = ANNType; Encoding = Encoding; @@ -18,7 +25,7 @@ export class AddModelComponent implements OnInit { Optimizer = Optimizer; Object = Object; - constructor() { + constructor(private models: ModelsService) { this.newModel = new Model(); } @@ -26,7 +33,64 @@ export class AddModelComponent implements OnInit { } addModel() { - //TODO + this.saveModel(false); //trajno cuvanje + } + trainModel() { + this.saveModel(true).subscribe((modelId : any) => { + if (modelId) + this.models.trainModel(modelId); + }); //privremeno cuvanje modela => vraca id sacuvanog modela koji cemo da treniramo sad + } + saveModel(temporary: boolean) : any { + if (this.datasetLoadComponent) + this.models.addDataset(this.datasetLoadComponent?.dataset).subscribe((response) => {//id dataseta je response + this.newModel.datasetId = response; + + this.getCheckedInputCols(); + this.getCheckedOutputCol(); + if (this.validationInputsOutput()) + return this.models.addModel(this.newModel); //id modela + return of(null); + }); + } + + getCheckedInputCols() { + this.newModel.inputColumns = []; + let checkboxes = document.getElementsByName("cbs"); + + for (let i = 0; i < checkboxes.length; i++) { + let thatCb = <HTMLInputElement>checkboxes[i]; + if (thatCb.checked) + this.newModel.inputColumns.push(thatCb.value); + } + //console.log(this.checkedInputCols); + } + getCheckedOutputCol() { + this.newModel.columnToPredict = ''; + let radiobuttons = document.getElementsByName("rbs"); + + for (let i = 0; i < radiobuttons.length; i++) { + let thatRb = <HTMLInputElement>radiobuttons[i]; + if (thatRb.checked) { + this.newModel.columnToPredict = thatRb.value; + break; + } + } + //console.log(this.checkedOutputCol); + } + validationInputsOutput() : boolean { + if (this.newModel.inputColumns.length == 0) { + alert("Molimo Vas da izaberete ulaznu kolonu/kolone za mrežu.") + return false; + } + for (let i = 0; i < this.newModel.inputColumns.length; i++) { + if (this.newModel.inputColumns[i] == this.newModel.columnToPredict) { + let colName = this.newModel.columnToPredict; + alert("Izabrali ste istu kolonu (" + colName + ") kao ulaznu i izlaznu iz mreže. Korigujte izbor."); + return false; + } + } + return true; } } diff --git a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.css b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.css diff --git a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.html b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.html new file mode 100644 index 00000000..fa38a1bc --- /dev/null +++ b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.html @@ -0,0 +1 @@ +<p>browse-datasets works!</p> diff --git a/frontend/src/app/_pages/only-authorized/only-authorized.component.spec.ts b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.spec.ts index 82a01f63..fda74dbe 100644 --- a/frontend/src/app/_pages/only-authorized/only-authorized.component.spec.ts +++ b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { OnlyAuthorizedComponent } from './only-authorized.component'; +import { BrowseDatasetsComponent } from './browse-datasets.component'; -describe('OnlyAuthorizedComponent', () => { - let component: OnlyAuthorizedComponent; - let fixture: ComponentFixture<OnlyAuthorizedComponent>; +describe('BrowseDatasetsComponent', () => { + let component: BrowseDatasetsComponent; + let fixture: ComponentFixture<BrowseDatasetsComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ OnlyAuthorizedComponent ] + declarations: [ BrowseDatasetsComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(OnlyAuthorizedComponent); + fixture = TestBed.createComponent(BrowseDatasetsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.ts b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.ts new file mode 100644 index 00000000..dba6c25e --- /dev/null +++ b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-browse-datasets', + templateUrl: './browse-datasets.component.html', + styleUrls: ['./browse-datasets.component.css'] +}) +export class BrowseDatasetsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.css b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.css diff --git a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.html b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.html new file mode 100644 index 00000000..01c4af82 --- /dev/null +++ b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.html @@ -0,0 +1 @@ +<p>browse-predictors works!</p> diff --git a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.spec.ts b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.spec.ts new file mode 100644 index 00000000..6d13fedf --- /dev/null +++ b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BrowsePredictorsComponent } from './browse-predictors.component'; + +describe('BrowsePredictorsComponent', () => { + let component: BrowsePredictorsComponent; + let fixture: ComponentFixture<BrowsePredictorsComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ BrowsePredictorsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BrowsePredictorsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.ts b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.ts new file mode 100644 index 00000000..b4fb2a9d --- /dev/null +++ b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-browse-predictors', + templateUrl: './browse-predictors.component.html', + styleUrls: ['./browse-predictors.component.css'] +}) +export class BrowsePredictorsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_pages/home/home.component.css b/frontend/src/app/_pages/home/home.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/home/home.component.css diff --git a/frontend/src/app/_pages/home/home.component.html b/frontend/src/app/_pages/home/home.component.html new file mode 100644 index 00000000..7245f602 --- /dev/null +++ b/frontend/src/app/_pages/home/home.component.html @@ -0,0 +1,53 @@ +<div class="d-flex flex-column align-items-center bg-light"> + <img src="../../../assets/svg/logo.svg" class="bi me-2" width="256" height="256" role="img"> + <div *ngIf="shared.loggedIn" class="d-flex flex-column align-items-center"> + <h2 class="my-4">Započnite sa treniranjem!</h2> + <div id="cards" class="row align-items-center justify-content-center"> + <div class="card shadow col-3 m-1" style="width: 18rem;"> + <div class="card-body"> + <mat-icon width="48px" height="48px" + style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">storage</mat-icon> + <h3 class="card-title my-2">Moji izvori podataka</h3> + <p class="card-text"> + <a class="stretched-link" routerLink="my-datasets">Preuredite</a> vaše izvore + podataka, ili + dodajte novi. + </p> + </div> + </div> + <div class="card shadow col-3 m-1" style="width: 18rem;"> + <div class="card-body"> + <mat-icon width="48px" height="48px" + style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">model_training + </mat-icon> + <h3 class="card-title my-2">Moji modeli</h3> + <p class="card-text"> + <a class="stretched-link" routerLink="my-models">Pregledajte</a> vaše modele, menjajte ih, + napravite nove modele, ili + ih obrišite. + </p> + </div> + </div> + <div class="card shadow col-3 m-1" style="width: 18rem;"> + <div class="card-body"> + <mat-icon width="48px" height="48px" + style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">batch_prediction + </mat-icon> + <h3 class="card-title my-2">Rezultati treniranja</h3> + <p class="card-text"> + <a class="stretched-link" routerLink="my-predictors">Pregledajte</a> sve vaše trenirane + modele, + koristite ih da predvidite vrednosti za red ili skup podataka, ili ih obrišite. + </p> + </div> + </div> + </div> + + </div> + <h2 class="my-4">Pogledajte javne izvore podataka!</h2> + <app-carousel [items]="publicDatasets"> + </app-carousel> + <h2 class="my-4">Iskoristite već trenirane modele!</h2> + <app-carousel [items]="publicPredictors"> + </app-carousel> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/home/home.component.spec.ts b/frontend/src/app/_pages/home/home.component.spec.ts new file mode 100644 index 00000000..2c5a1726 --- /dev/null +++ b/frontend/src/app/_pages/home/home.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HomeComponent } from './home.component'; + +describe('HomeComponent', () => { + let component: HomeComponent; + let fixture: ComponentFixture<HomeComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ HomeComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HomeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/home/home.component.ts b/frontend/src/app/_pages/home/home.component.ts new file mode 100644 index 00000000..7e4471e8 --- /dev/null +++ b/frontend/src/app/_pages/home/home.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit } from '@angular/core'; +import Dataset from 'src/app/_data/Dataset'; +import Predictor from 'src/app/_data/Predictor'; +import { ItemDatasetComponent } from 'src/app/_elements/item-dataset/item-dataset.component'; +import shared from 'src/app/Shared'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.css'] +}) +export class HomeComponent implements OnInit { + + publicDatasets: Dataset[]; + publicPredictors: Predictor[]; + + shared = shared; + + constructor() { + this.publicDatasets = [ + new Dataset('Titanik', 'Titanik', ['Kolona1', 'Kolona2', 'Ime', 'OsobaJePreživela']), + new Dataset('Drugi Dataset', 'Lorem ipsum dolor sir amet', ['jabuka', 'kruska', 'jagoda']), + new Dataset('Dataset III', 'Kratak opis izvora podataka', ['c1', 'c2', 'c3', 'c4', 'c5']), + new Dataset('Drugi Dataset', 'Lorem ipsum dolor sir amet', ['jabuka', 'kruska', 'jagoda']), + new Dataset('Dataset III', 'Kratak opis izvora podataka', ['c1', 'c2', 'c3', 'c4', 'c5']), + new Dataset('Drugi Dataset', 'Lorem ipsum dolor sir amet', ['jabuka', 'kruska', 'jagoda']), + new Dataset('Dataset III', 'Kratak opis izvora podataka', ['c1', 'c2', 'c3', 'c4', 'c5']), + new Dataset('Dataset III', 'Kratak opis izvora podataka', ['c1', 'c2', 'c3', 'c4', 'c5']) + ] + this.publicPredictors = [ + new Predictor('Preživeli', 'Za uneto ime osobe, predvidja da li je ta osoba preživela ili ne.', ['Ime'], 'OsobaJePreživela'), + new Predictor('Drugi model', 'Lorem ipsum dolor sir amet', ['kruska'], 'jagoda'), + new Predictor('Treći model', 'Kratak opis modela', ['c1', 'c2', 'c3'], 'c5'), + new Predictor('Drugi model', 'Lorem ipsum dolor sir amet', ['kruska'], 'jagoda'), + new Predictor('Treći model', 'Kratak opis modela', ['c1', 'c2', 'c3'], 'c5'), + new Predictor('Drugi model', 'Lorem ipsum dolor sir amet', ['kruska'], 'jagoda'), + new Predictor('Treći model', 'Kratak opis modela', ['c1', 'c2', 'c3'], 'c5'), + new Predictor('Treći model', 'Kratak opis modela', ['c1', 'c2', 'c3'], 'c5') + ] + } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_pages/login-page/login-page.component.html b/frontend/src/app/_pages/login-page/login-page.component.html deleted file mode 100644 index 8deb5290..00000000 --- a/frontend/src/app/_pages/login-page/login-page.component.html +++ /dev/null @@ -1,55 +0,0 @@ -<!--<script> - $(document).ready(function(){ - $(".btn").click(function(){ - $("#exampleModal").modal('show'); - }); - - $('#exampleModal').modal({ - backdrop: 'static', - keyboard: false - }); - }); -</script>--> - -<!-- Button trigger modal --> -<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalForLogin" (click)="openModal()"> - Open Modal - </button> - -<!-- -<div style="min-height: 100vh; position: relative;"> - - <div class="container p-5 rounded-3 shadow-sm border" style="max-width: 50em; margin-top: 50px;"> - <h3 class="text-center pb-5">Prijavite se</h3> - <form> - <div class="form-outline mb-4"> - <label class="form-label" for="username">Korisničko ime</label> - <input [(ngModel)]="username" name="username" type="text" id="username" - class="form-control form-control-lg" placeholder="Unesite korisničko ime..." /> - </div> - - <div class="form-outline mb-3"> - <label class="form-label" for="password">Lozinka</label> - <input [(ngModel)]="password" name="password" type="password" id="password" - class="form-control form-control-lg" placeholder="Unesite lozinku..." /> - </div> - - <div class="text-center text-lg-start mt-4 pt-2"> - <p *ngIf="wrongCreds" class="small fw-bold mt-2 pt-1 mb-0 text-danger">Lozinka ili e-mail su pogrešni - </p> - <br> - - <button type="button" class="btn btn-primary btn-lg" - style="padding-left: 2.5rem; padding-right: 2.5rem;" (click)="onSubmit()">Prijava - </button> - - <br> - <p class="small fw-bold mt-2 pt-1 mb-0">Još uvek nemate nalog? - <a routerLink="/register" class="link-danger">Registrujte se</a> - </p> - </div> - </form> - </div> - -</div> --->
\ No newline at end of file diff --git a/frontend/src/app/_pages/login-page/login-page.component.ts b/frontend/src/app/_pages/login-page/login-page.component.ts deleted file mode 100644 index e5366283..00000000 --- a/frontend/src/app/_pages/login-page/login-page.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; -import { CookieService } from 'ngx-cookie-service'; -import { AuthService } from 'src/app/_services/auth.service'; - -import { LoginModalComponent } from 'src/app/_modals/login-modal/login-modal.component'; -import { MDBModalRef, MDBModalService } from 'ng-uikit-pro-standard'; - - -declare var window: any; - -@Component({ - selector: 'app-login-page', - templateUrl: './login-page.component.html', - styleUrls: ['./login-page.component.css'], - -}) -export class LoginPageComponent{ - - modalRef?: MDBModalRef; - - //email: string = ''; - username: string = ''; - password: string = ''; - - public wrongCreds: boolean = false; //RAZMOTRITI - //public notApproved: boolean = false; //RAZMOTRITI - - formModal: any; - - constructor( - private authService: AuthService, - private cookie: CookieService, - private router: Router, - private modalService: MDBModalService - ) { } - - openModal() { - //this.modalRef = this.modalService.show(LoginModalComponent); - } - /* - ngOnInit(): void { - this.formModal = new window.bootstrap.Modal( - document.getElementById("exampleModal") - ); - } - - openModal() { - this.formModal.show(); - //console.log("ok"); - //(<HTMLInputElement>document.getElementById("exampleModal")).style.display = "block"; - } - - onSubmit() { - - this.authService.login(this.username, this.password).subscribe((response) => { - console.log(response); - this.cookie.set('token', response); - this.router.navigate(['add-model']); - }); - } -*/ -} diff --git a/frontend/src/app/_pages/my-datasets/my-datasets.component.css b/frontend/src/app/_pages/my-datasets/my-datasets.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/my-datasets/my-datasets.component.css diff --git a/frontend/src/app/_pages/my-datasets/my-datasets.component.html b/frontend/src/app/_pages/my-datasets/my-datasets.component.html new file mode 100644 index 00000000..4b3095be --- /dev/null +++ b/frontend/src/app/_pages/my-datasets/my-datasets.component.html @@ -0,0 +1 @@ +<p>my-datasets works!</p> diff --git a/frontend/src/app/_pages/my-datasets/my-datasets.component.spec.ts b/frontend/src/app/_pages/my-datasets/my-datasets.component.spec.ts new file mode 100644 index 00000000..fc1fc3f3 --- /dev/null +++ b/frontend/src/app/_pages/my-datasets/my-datasets.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MyDatasetsComponent } from './my-datasets.component'; + +describe('MyDatasetsComponent', () => { + let component: MyDatasetsComponent; + let fixture: ComponentFixture<MyDatasetsComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MyDatasetsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MyDatasetsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/my-datasets/my-datasets.component.ts b/frontend/src/app/_pages/my-datasets/my-datasets.component.ts new file mode 100644 index 00000000..af5eab97 --- /dev/null +++ b/frontend/src/app/_pages/my-datasets/my-datasets.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-my-datasets', + templateUrl: './my-datasets.component.html', + styleUrls: ['./my-datasets.component.css'] +}) +export class MyDatasetsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_pages/my-models/my-models.component.css b/frontend/src/app/_pages/my-models/my-models.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/my-models/my-models.component.css diff --git a/frontend/src/app/_pages/my-models/my-models.component.html b/frontend/src/app/_pages/my-models/my-models.component.html new file mode 100644 index 00000000..2fea257d --- /dev/null +++ b/frontend/src/app/_pages/my-models/my-models.component.html @@ -0,0 +1 @@ +<p>my-models works!</p> diff --git a/frontend/src/app/_pages/my-models/my-models.component.spec.ts b/frontend/src/app/_pages/my-models/my-models.component.spec.ts new file mode 100644 index 00000000..e431d04c --- /dev/null +++ b/frontend/src/app/_pages/my-models/my-models.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MyModelsComponent } from './my-models.component'; + +describe('MyModelsComponent', () => { + let component: MyModelsComponent; + let fixture: ComponentFixture<MyModelsComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MyModelsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MyModelsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/my-models/my-models.component.ts b/frontend/src/app/_pages/my-models/my-models.component.ts new file mode 100644 index 00000000..e9bc52de --- /dev/null +++ b/frontend/src/app/_pages/my-models/my-models.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-my-models', + templateUrl: './my-models.component.html', + styleUrls: ['./my-models.component.css'] +}) +export class MyModelsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.css b/frontend/src/app/_pages/my-predictors/my-predictors.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/my-predictors/my-predictors.component.css diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.html b/frontend/src/app/_pages/my-predictors/my-predictors.component.html new file mode 100644 index 00000000..32d085af --- /dev/null +++ b/frontend/src/app/_pages/my-predictors/my-predictors.component.html @@ -0,0 +1 @@ +<p>my-predictors works!</p> diff --git a/frontend/src/app/_pages/register-page/register-page.component.spec.ts b/frontend/src/app/_pages/my-predictors/my-predictors.component.spec.ts index 347fe9f4..37dddf6d 100644 --- a/frontend/src/app/_pages/register-page/register-page.component.spec.ts +++ b/frontend/src/app/_pages/my-predictors/my-predictors.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RegisterPageComponent } from './register-page.component'; +import { MyPredictorsComponent } from './my-predictors.component'; -describe('RegisterPageComponent', () => { - let component: RegisterPageComponent; - let fixture: ComponentFixture<RegisterPageComponent>; +describe('MyPredictorsComponent', () => { + let component: MyPredictorsComponent; + let fixture: ComponentFixture<MyPredictorsComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ RegisterPageComponent ] + declarations: [ MyPredictorsComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(RegisterPageComponent); + fixture = TestBed.createComponent(MyPredictorsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.ts b/frontend/src/app/_pages/my-predictors/my-predictors.component.ts new file mode 100644 index 00000000..b0d6e9dd --- /dev/null +++ b/frontend/src/app/_pages/my-predictors/my-predictors.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-my-predictors', + templateUrl: './my-predictors.component.html', + styleUrls: ['./my-predictors.component.css'] +}) +export class MyPredictorsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_pages/only-authorized/only-authorized.component.html b/frontend/src/app/_pages/only-authorized/only-authorized.component.html deleted file mode 100644 index 27bbcf09..00000000 --- a/frontend/src/app/_pages/only-authorized/only-authorized.component.html +++ /dev/null @@ -1 +0,0 @@ -<p>only-authorized works!</p> diff --git a/frontend/src/app/_pages/only-authorized/only-authorized.component.ts b/frontend/src/app/_pages/only-authorized/only-authorized.component.ts deleted file mode 100644 index be88365f..00000000 --- a/frontend/src/app/_pages/only-authorized/only-authorized.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-only-authorized', - templateUrl: './only-authorized.component.html', - styleUrls: ['./only-authorized.component.css'] -}) -export class OnlyAuthorizedComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} diff --git a/frontend/src/app/_pages/predict/predict.component.css b/frontend/src/app/_pages/predict/predict.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/predict/predict.component.css diff --git a/frontend/src/app/_pages/predict/predict.component.html b/frontend/src/app/_pages/predict/predict.component.html new file mode 100644 index 00000000..74a83b71 --- /dev/null +++ b/frontend/src/app/_pages/predict/predict.component.html @@ -0,0 +1 @@ +<p>predict works!</p> diff --git a/frontend/src/app/_pages/predict/predict.component.spec.ts b/frontend/src/app/_pages/predict/predict.component.spec.ts new file mode 100644 index 00000000..65871ecc --- /dev/null +++ b/frontend/src/app/_pages/predict/predict.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PredictComponent } from './predict.component'; + +describe('PredictComponent', () => { + let component: PredictComponent; + let fixture: ComponentFixture<PredictComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PredictComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PredictComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/predict/predict.component.ts b/frontend/src/app/_pages/predict/predict.component.ts new file mode 100644 index 00000000..0e313c65 --- /dev/null +++ b/frontend/src/app/_pages/predict/predict.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-predict', + templateUrl: './predict.component.html', + styleUrls: ['./predict.component.css'] +}) +export class PredictComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_pages/profile/profile.component.css b/frontend/src/app/_pages/profile/profile.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/profile/profile.component.css diff --git a/frontend/src/app/_pages/profile/profile.component.html b/frontend/src/app/_pages/profile/profile.component.html new file mode 100644 index 00000000..9df0576d --- /dev/null +++ b/frontend/src/app/_pages/profile/profile.component.html @@ -0,0 +1 @@ +<p>profile works!</p> diff --git a/frontend/src/app/_pages/profile/profile.component.spec.ts b/frontend/src/app/_pages/profile/profile.component.spec.ts new file mode 100644 index 00000000..e88012e7 --- /dev/null +++ b/frontend/src/app/_pages/profile/profile.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProfileComponent } from './profile.component'; + +describe('ProfileComponent', () => { + let component: ProfileComponent; + let fixture: ComponentFixture<ProfileComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ProfileComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ProfileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/profile/profile.component.ts b/frontend/src/app/_pages/profile/profile.component.ts new file mode 100644 index 00000000..93040f3e --- /dev/null +++ b/frontend/src/app/_pages/profile/profile.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-profile', + templateUrl: './profile.component.html', + styleUrls: ['./profile.component.css'] +}) +export class ProfileComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_pages/register-page/register-page.component.html b/frontend/src/app/_pages/register-page/register-page.component.html deleted file mode 100644 index f8ae046e..00000000 --- a/frontend/src/app/_pages/register-page/register-page.component.html +++ /dev/null @@ -1,80 +0,0 @@ -<div style="min-height: 100vh; position: relative;"> - - <!-- TODO <app-navbar [activeNav]="'register'"></app-navbar>--> - - <div class="container" style="margin-top: 50px;"> - <div class="text-black"> - <div class="row justify-content-center"> - <div class="col-8 bg-white border rounded-3 shadow-sm p-5"> - <p class="text-center h2 fw-bold mb-5 mx-1 mx-md-4">Registracija</p> - - <form class="mx-1 mx-md-4"> - <!--Ime--> - <div class="row"> - <div class="col-6 p-2"> - <label class="form-label" for="firstName">Ime</label> - <input type="text" id="firstName" class="form-control" [(ngModel)]="firstName" name="firstName"> - <p *ngIf="wrongFirstNameBool" class="small fw-bold text-danger">Unesite ispravno ime. (minimum 1, maksimum 30 karaktera)</p> - </div> - <!--Prezime--> - <div class="col-6 p-2"> - <label class="form-label" for="lastName">Prezime</label> - <input type="text" id="lastName" class="form-control" [(ngModel)]="lastName" name="lastName" /> - <p *ngIf="wrongLastNameBool" class="small fw-bold text-danger">Unesite ispravno prezime. (minimum 1, maksimum 30 karaktera)</p> - </div> - </div> - <br> - - <div class="row"> - <!--Korisnicko ime--> - <div class="col-6 p-2"> - <label class="form-label" for="nickName">Korisničko ime</label> - <input type="text" id="nickName" class="form-control" [(ngModel)]="nickName" name="nickName" /> - <p *ngIf="wrongNickNameBool" class="small fw-bold text-danger">Unesite ispravno korisničko ime.</p> - </div> - <!--Email--> - <div class="col-6 p-2"> - <label class="form-label" for="email">E-mail adresa</label> - <input type="email" id="email" class="form-control" [(ngModel)]="email" name="email" /> - <p *ngIf="wrongEmailBool" class="small fw-bold text-danger">Unesite ispravnu e-mail adresu.</p> - </div> - </div> - <br> - - <div class="row"> - <!-- Lozinka 1. --> - <div class="col-6 p-2"> - <label class="form-label" for="pass1">Lozinka</label> - <input type="password" id="pass1" class="form-control" [(ngModel)]="pass1" name="pass1" /> - <p *ngIf="wrongPass1Bool" class="small fw-bold text-danger">Lozinka se mora sastojati od najmanje 6 karaktera.</p> - </div> - - <!-- Lozinka 2. --> - <div class="col-6 p-2"> - <label class="form-label" for="pass2">Potvrdite lozinku</label> - <input type="password" id="pass2" class="form-control" [(ngModel)]="pass2" name="pass2" /> - <p *ngIf="wrongPass2Bool" class="small fw-bold text-danger">Lozinke se ne podudaraju.</p> - </div> - </div> - - <br><br><br> - <!--Dugme Registruj se--> - <div class="d-flex justify-content-center mx-4 mb-3 mb-lg-4"> - <button type="button" class="btn btn-primary btn-lg" (click)="validation()">Registrujte se</button> - </div> - - <div class="form-check d-flex justify-content-center mb-5"> - <label class="form-check-label" class="small fw-bold mt-2 pt-1 mb-0" for="form2Example3"> - Već imate kreiran nalog? - <a routerLink="/login" class="link-danger">Prijavite se</a> - </label> - </div> - </form> - </div> - </div> - </div> - </div> - - <!-- TODO <app-footer></app-footer> --> - -</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/settings/settings.component.css b/frontend/src/app/_pages/settings/settings.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/settings/settings.component.css diff --git a/frontend/src/app/_pages/settings/settings.component.html b/frontend/src/app/_pages/settings/settings.component.html new file mode 100644 index 00000000..4ab2a415 --- /dev/null +++ b/frontend/src/app/_pages/settings/settings.component.html @@ -0,0 +1 @@ +<p>settings works!</p> diff --git a/frontend/src/app/_pages/settings/settings.component.spec.ts b/frontend/src/app/_pages/settings/settings.component.spec.ts new file mode 100644 index 00000000..a3a508b0 --- /dev/null +++ b/frontend/src/app/_pages/settings/settings.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SettingsComponent } from './settings.component'; + +describe('SettingsComponent', () => { + let component: SettingsComponent; + let fixture: ComponentFixture<SettingsComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SettingsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/settings/settings.component.ts b/frontend/src/app/_pages/settings/settings.component.ts new file mode 100644 index 00000000..19862fb0 --- /dev/null +++ b/frontend/src/app/_pages/settings/settings.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-settings', + templateUrl: './settings.component.html', + styleUrls: ['./settings.component.css'] +}) +export class SettingsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_services/auth-guard.service.ts b/frontend/src/app/_services/auth-guard.service.ts index b6d3678d..057996e0 100644 --- a/frontend/src/app/_services/auth-guard.service.ts +++ b/frontend/src/app/_services/auth-guard.service.ts @@ -15,7 +15,7 @@ export class AuthGuardService implements CanActivate { if (this.auth.isAuthenticated()) { return true; } - this.router.navigate(['login']); + this.router.navigate(['']); return false; } } diff --git a/frontend/src/app/_services/auth.service.ts b/frontend/src/app/_services/auth.service.ts index c96c2dae..afc1567b 100644 --- a/frontend/src/app/_services/auth.service.ts +++ b/frontend/src/app/_services/auth.service.ts @@ -3,6 +3,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { JwtHelperService } from '@auth0/angular-jwt'; import { CookieService } from 'ngx-cookie-service'; import { API_SETTINGS } from 'src/config'; +import shared from '../Shared'; const jwtHelper = new JwtHelperService(); @@ -11,6 +12,8 @@ const jwtHelper = new JwtHelperService(); }) export class AuthService { + shared = shared; + constructor(private http: HttpClient, private cookie: CookieService) { } login(username: string, password: string) { @@ -28,4 +31,49 @@ export class AuthService { } return false; } + + lastToken?: string; + refresher: any; + + enableAutoRefresh() { + this.lastToken = this.cookie.get('token'); + let exp = jwtHelper.getTokenExpirationDate(this.lastToken); + if (!exp) { + exp = new Date(); + } + this.refresher = setTimeout(() => { + console.log('refreshing token!'); + this.http.post(`${API_SETTINGS.apiURL}/auth/renewJwt`, {}, { headers: this.authHeader(), responseType: 'text' }).subscribe((response) => { + this.authenticate(response); + }); + }, exp.getTime() - new Date().getTime()); + } + + authenticate(token: string) { + let exp = jwtHelper.getTokenExpirationDate(token); + if (!exp) { + exp = new Date(); + } + this.cookie.set('token', token, exp); + this.updateUser(); + } + + updateUser() { + if (this.cookie.check('token')) { + const token = this.cookie.get('token'); + this.shared.loggedIn = this.isAuthenticated(); + this.enableAutoRefresh(); + } + } + + logOut() { + this.cookie.delete('token'); + if (this.refresher) + clearTimeout(this.refresher); + this.shared.loggedIn = false; + } + + authHeader() { + return new HttpHeaders().set("Authorization", "Bearer " + this.cookie.get('token')); + } } diff --git a/frontend/src/app/_services/models.service.spec.ts b/frontend/src/app/_services/models.service.spec.ts new file mode 100644 index 00000000..b5b25752 --- /dev/null +++ b/frontend/src/app/_services/models.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ModelsService } from './models.service'; + +describe('ModelsService', () => { + let service: ModelsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ModelsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_services/models.service.ts b/frontend/src/app/_services/models.service.ts new file mode 100644 index 00000000..80dc1ae3 --- /dev/null +++ b/frontend/src/app/_services/models.service.ts @@ -0,0 +1,25 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import Model from '../_data/Model'; +import { AuthService } from './auth.service'; +import { API_SETTINGS } from 'src/config'; +import Dataset from '../_data/Dataset'; + + +@Injectable({ + providedIn: 'root' +}) +export class ModelsService { + + constructor(private http: HttpClient, private authService: AuthService) { } + + addModel(model: Model) { + return this.http.post(`${API_SETTINGS.apiURL}/model/add`, model, { headers: this.authService.authHeader(), responseType: 'text' }); + } + addDataset(dataset: Dataset) { + return this.http.post(`${API_SETTINGS.apiURL}/dataset/add`, dataset, { headers: this.authService.authHeader(), responseType: 'text' }); + } + trainModel(modelId: string) { + return this.http.post(`${API_SETTINGS.apiURL}/model/train`, modelId, { headers: this.authService.authHeader(), responseType: 'text' }); + } +} diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index cd86ef5c..ee43b522 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -2,19 +2,28 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AuthGuardService } from './_services/auth-guard.service'; -import { LoginPageComponent } from './_pages/login-page/login-page.component'; -import { OnlyAuthorizedComponent } from './_pages/only-authorized/only-authorized.component'; -import { RegisterPageComponent } from './_pages/register-page/register-page.component'; import { AddModelComponent } from './_pages/add-model/add-model.component'; -import { LoginModalComponent } from './_modals/login-modal/login-modal.component'; +import { HomeComponent } from './_pages/home/home.component'; +import { MyDatasetsComponent } from './_pages/my-datasets/my-datasets.component'; +import { MyModelsComponent } from './_pages/my-models/my-models.component'; +import { MyPredictorsComponent } from './_pages/my-predictors/my-predictors.component'; +import { BrowsePredictorsComponent } from './_pages/browse-predictors/browse-predictors.component'; +import { BrowseDatasetsComponent } from './_pages/browse-datasets/browse-datasets.component'; +import { SettingsComponent } from './_pages/settings/settings.component'; +import { ProfileComponent } from './_pages/profile/profile.component'; +import { PredictComponent } from './_pages/predict/predict.component'; const routes: Routes = [ - { path: '', redirectTo: '/login', pathMatch: 'full' }, - { path: 'login', component: LoginPageComponent }, - { path: 'register', component: RegisterPageComponent }, - { path: 'only-authorized', component: OnlyAuthorizedComponent, canActivate: [AuthGuardService] }, + { path: '', component: HomeComponent }, { path: 'add-model', component: AddModelComponent }, - { path: 'login-modal-test', component: LoginModalComponent } + { path: 'my-datasets', component: MyDatasetsComponent, canActivate: [AuthGuardService] }, + { path: 'my-models', component: MyModelsComponent, canActivate: [AuthGuardService] }, + { path: 'my-predictors', component: MyPredictorsComponent, canActivate: [AuthGuardService] }, + { path: 'settings', component: SettingsComponent, canActivate: [AuthGuardService] }, + { path: 'profile', component: ProfileComponent, canActivate: [AuthGuardService] }, + { path: 'browse-datasets', component: BrowseDatasetsComponent }, + { path: 'browse-predictors', component: BrowsePredictorsComponent }, + { path: 'predict', component: PredictComponent } ]; @NgModule({ diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 90c6b646..35e5adb1 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1 +1,4 @@ -<router-outlet></router-outlet>
\ No newline at end of file +<app-navbar></app-navbar> +<div class="container h-100"> + <router-outlet></router-outlet> +</div>
\ No newline at end of file diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index d95252ad..5aa405fb 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -3,29 +3,53 @@ import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { HttpClientModule } from '@angular/common/http'; +import { MatSliderModule } from '@angular/material/slider'; +import { MatIconModule } from '@angular/material/icon'; import { AppComponent } from './app.component'; -import { LoginPageComponent } from './_pages/login-page/login-page.component'; -import { RegisterPageComponent } from './_pages/register-page/register-page.component'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { OnlyAuthorizedComponent } from './_pages/only-authorized/only-authorized.component'; import { DatasetLoadComponent } from './_elements/dataset-load/dataset-load.component'; import { AddModelComponent } from './_pages/add-model/add-model.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { LoginModalComponent } from './_modals/login-modal/login-modal.component'; +import { ReactiveFormsModule } from '@angular/forms'; +import { RegisterModalComponent } from './_modals/register-modal/register-modal.component'; import { MaterialModule } from './material.module'; -import { ReactiveFormsModule } from '@angular/forms'; +import { HomeComponent } from './_pages/home/home.component'; +import { NavbarComponent } from './_elements/navbar/navbar.component'; +import { ItemPredictorComponent } from './_elements/item-predictor/item-predictor.component'; +import { ItemDatasetComponent } from './_elements/item-dataset/item-dataset.component'; +import { CarouselComponent } from './_elements/carousel/carousel.component'; +import { SettingsComponent } from './_pages/settings/settings.component'; +import { ProfileComponent } from './_pages/profile/profile.component'; +import { MyPredictorsComponent } from './_pages/my-predictors/my-predictors.component'; +import { MyDatasetsComponent } from './_pages/my-datasets/my-datasets.component'; +import { MyModelsComponent } from './_pages/my-models/my-models.component'; +import { BrowseDatasetsComponent } from './_pages/browse-datasets/browse-datasets.component'; +import { BrowsePredictorsComponent } from './_pages/browse-predictors/browse-predictors.component'; +import { PredictComponent } from './_pages/predict/predict.component'; @NgModule({ declarations: [ AppComponent, - LoginPageComponent, - RegisterPageComponent, - OnlyAuthorizedComponent, DatasetLoadComponent, AddModelComponent, - LoginModalComponent + LoginModalComponent, + RegisterModalComponent, + HomeComponent, + NavbarComponent, + ItemPredictorComponent, + ItemDatasetComponent, + CarouselComponent, + SettingsComponent, + ProfileComponent, + MyPredictorsComponent, + MyDatasetsComponent, + MyModelsComponent, + BrowseDatasetsComponent, + BrowsePredictorsComponent, + PredictComponent ], imports: [ BrowserModule, @@ -35,7 +59,9 @@ import { ReactiveFormsModule } from '@angular/forms'; NgbModule, BrowserAnimationsModule, MaterialModule, - ReactiveFormsModule + ReactiveFormsModule, + MatSliderModule, + MatIconModule ], providers: [], bootstrap: [AppComponent] diff --git a/frontend/src/assets/images/add_model_background.jpg b/frontend/src/assets/images/add_model_background.jpg Binary files differnew file mode 100644 index 00000000..d86f0566 --- /dev/null +++ b/frontend/src/assets/images/add_model_background.jpg diff --git a/frontend/src/assets/images/logo.png b/frontend/src/assets/images/logo.png Binary files differnew file mode 100644 index 00000000..2e15550a --- /dev/null +++ b/frontend/src/assets/images/logo.png diff --git a/frontend/src/assets/images/logo_dark.png b/frontend/src/assets/images/logo_dark.png Binary files differnew file mode 100644 index 00000000..95c06d8f --- /dev/null +++ b/frontend/src/assets/images/logo_dark.png diff --git a/frontend/src/assets/svg/logo.svg b/frontend/src/assets/svg/logo.svg new file mode 100644 index 00000000..cd79cd55 --- /dev/null +++ b/frontend/src/assets/svg/logo.svg @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="100px" height="100px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:none;} + .st1{fill:#00A8E8;} + .st2{fill:#007EA7;} + .st3{fill:#FFFFFF;} +</style> +<g id="XMLID_455_"> + <g id="XMLID_357_"> + <g id="XMLID_317_"> + <rect id="XMLID_30_" x="-18.1" y="76.3" class="st0" width="138.3" height="23.7"/> + <path id="XMLID_2_" class="st1" d="M10,79.1v3.6h0.4c0.3,0,0.5,0.1,0.6,0.2s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4 + s-0.3,0.2-0.6,0.2H8.9c-0.3,0-0.5-0.1-0.6-0.2s-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2V78H8.6 + C8.3,78,8.1,78,8,77.9s-0.2-0.3-0.2-0.4S7.9,77.1,8,77s0.3-0.2,0.6-0.2l1.4,0l3.1,4.8V78h-0.4c-0.3,0-0.5-0.1-0.6-0.2 + s-0.2-0.3-0.2-0.4s0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2l1.6,0c0.3,0,0.5,0.1,0.6,0.2s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4 + S14.5,78,14.3,78v5.9h-1.2L10,79.1z"/> + <path id="XMLID_4_" class="st1" d="M21.7,81.9h-4.9c0.1,0.3,0.3,0.6,0.7,0.8s0.7,0.3,1.3,0.3c0.4,0,1-0.1,1.8-0.3 + c0.3-0.1,0.5-0.1,0.6-0.1c0.2,0,0.3,0.1,0.4,0.2s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4c-0.2,0.1-0.5,0.3-1.1,0.4 + s-1.2,0.2-1.7,0.2c-1,0-1.7-0.3-2.3-0.8s-0.9-1.2-0.9-2c0-0.8,0.3-1.5,0.9-2.1s1.3-0.8,2.2-0.8c0.5,0,0.9,0.1,1.3,0.3 + s0.7,0.4,0.9,0.6c0.3,0.3,0.5,0.6,0.7,1.1c0.1,0.3,0.2,0.6,0.2,1V81.9z M20.4,80.7c-0.2-0.3-0.4-0.6-0.7-0.8s-0.7-0.3-1.1-0.3 + c-0.4,0-0.8,0.1-1.1,0.3s-0.5,0.4-0.7,0.8H20.4z"/> + <path id="XMLID_7_" class="st1" d="M28.5,78.6v4.1c0.3,0,0.4,0.1,0.6,0.2s0.2,0.3,0.2,0.4s-0.1,0.3-0.2,0.4s-0.3,0.2-0.6,0.2 + h-1.1v-0.3c-0.3,0.2-0.7,0.3-1,0.4s-0.6,0.1-0.9,0.1c-0.4,0-0.7-0.1-1-0.2s-0.5-0.4-0.7-0.7c-0.1-0.2-0.2-0.5-0.2-0.8v-2.6h-0.2 + c-0.3,0-0.5-0.1-0.6-0.2s-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2h1.4v3.6c0,0.3,0.1,0.4,0.2,0.6s0.3,0.2,0.6,0.2 + c0.2,0,0.5,0,0.8-0.1s0.6-0.3,1-0.5v-2.4h-0.4c-0.3,0-0.5-0.1-0.6-0.2S26,79.4,26,79.2c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2 + H28.5z"/> + <path id="XMLID_9_" class="st1" d="M32.9,78.6v0.8c0.5-0.4,0.9-0.6,1.2-0.7s0.6-0.2,0.8-0.2c0.4,0,0.8,0.1,1.1,0.4 + c0.3,0.2,0.4,0.4,0.4,0.6c0,0.2-0.1,0.3-0.2,0.4s-0.3,0.2-0.4,0.2c-0.1,0-0.3-0.1-0.5-0.2s-0.3-0.2-0.4-0.2 + c-0.2,0-0.4,0.1-0.8,0.3s-0.8,0.5-1.3,0.9v1.8h1.7c0.3,0,0.5,0.1,0.6,0.2s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4 + s-0.3,0.2-0.6,0.2h-3.6c-0.3,0-0.5-0.1-0.6-0.2s-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2h0.7v-2.9h-0.4 + c-0.3,0-0.5-0.1-0.6-0.2s-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2H32.9z"/> + <path id="XMLID_11_" class="st1" d="M43.4,81.4c0,0.5-0.1,0.9-0.4,1.3s-0.6,0.8-1.1,1s-1,0.4-1.6,0.4c-0.5,0-1.1-0.1-1.6-0.4 + s-0.9-0.6-1.1-1s-0.4-0.9-0.4-1.4c0-0.5,0.1-1,0.4-1.4s0.6-0.8,1.1-1.1s1-0.4,1.6-0.4c0.5,0,1.1,0.1,1.6,0.4s0.9,0.6,1.1,1.1 + S43.4,80.9,43.4,81.4z M42.2,81.4c0-0.4-0.1-0.7-0.4-1.1c-0.4-0.4-0.9-0.7-1.5-0.7c-0.5,0-1,0.2-1.4,0.5s-0.5,0.8-0.5,1.2 + c0,0.4,0.2,0.7,0.6,1.1s0.8,0.5,1.4,0.5c0.5,0,1-0.2,1.4-0.5S42.2,81.8,42.2,81.4z"/> + <path id="XMLID_14_" class="st1" d="M45.8,83.6c-0.1,0.1-0.2,0.2-0.3,0.2s-0.1,0.1-0.2,0.1c-0.2,0-0.3-0.1-0.4-0.2 + s-0.2-0.3-0.2-0.6v-0.8c0-0.3,0.1-0.5,0.2-0.6s0.3-0.2,0.4-0.2c0.1,0,0.3,0,0.4,0.1s0.2,0.2,0.2,0.4s0.1,0.3,0.2,0.4 + c0.1,0.1,0.3,0.2,0.6,0.4s0.6,0.2,0.9,0.2c0.5,0,1-0.1,1.3-0.4c0.2-0.2,0.3-0.3,0.3-0.6c0-0.1-0.1-0.3-0.2-0.4s-0.3-0.2-0.5-0.3 + c-0.2-0.1-0.5-0.1-1-0.2c-0.7-0.1-1.2-0.3-1.5-0.4s-0.6-0.4-0.8-0.7s-0.3-0.7-0.3-1c0-0.6,0.2-1.1,0.7-1.5s1.1-0.6,1.9-0.6 + c0.3,0,0.6,0,0.9,0.1s0.5,0.2,0.7,0.3c0.2-0.2,0.3-0.2,0.5-0.2c0.2,0,0.3,0.1,0.4,0.2s0.2,0.3,0.2,0.6v0.9c0,0.3-0.1,0.5-0.2,0.6 + s-0.3,0.2-0.4,0.2c-0.1,0-0.3,0-0.4-0.1C49.1,79.1,49,79,49,78.8s-0.1-0.3-0.2-0.4c-0.1-0.1-0.3-0.3-0.5-0.4s-0.5-0.2-0.8-0.2 + c-0.4,0-0.8,0.1-1,0.3s-0.4,0.4-0.4,0.6c0,0.1,0.1,0.3,0.2,0.4s0.3,0.2,0.5,0.3c0.1,0.1,0.5,0.1,1.1,0.3s1.1,0.3,1.4,0.4 + s0.6,0.4,0.8,0.7s0.3,0.7,0.3,1.1c0,0.6-0.2,1.1-0.6,1.4c-0.6,0.5-1.3,0.7-2.1,0.7c-0.3,0-0.7,0-1-0.1S46.1,83.8,45.8,83.6z"/> + <path id="XMLID_16_" class="st1" d="M54.4,79.8v2.4c0,0.3,0.1,0.4,0.2,0.5c0.2,0.1,0.5,0.2,0.9,0.2c0.6,0,1.2-0.1,1.7-0.4 + c0.2-0.1,0.4-0.2,0.5-0.2c0.2,0,0.3,0.1,0.4,0.2s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4c-0.2,0.2-0.6,0.4-1.1,0.5s-1,0.2-1.4,0.2 + c-0.7,0-1.3-0.2-1.7-0.5s-0.6-0.7-0.6-1.2v-2.6h-0.4c-0.3,0-0.5-0.1-0.6-0.2S52,79.4,52,79.2c0-0.2,0.1-0.3,0.2-0.4 + s0.3-0.2,0.6-0.2h0.4v-1.1c0-0.3,0.1-0.5,0.2-0.6s0.3-0.2,0.4-0.2c0.2,0,0.3,0.1,0.4,0.2s0.2,0.3,0.2,0.6v1.1h2.2 + c0.3,0,0.5,0.1,0.6,0.2s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4s-0.3,0.2-0.6,0.2H54.4z"/> + <path id="XMLID_18_" class="st1" d="M64.9,81.9H60c0.1,0.3,0.3,0.6,0.7,0.8s0.7,0.3,1.3,0.3c0.4,0,1-0.1,1.8-0.3 + c0.3-0.1,0.5-0.1,0.6-0.1c0.2,0,0.3,0.1,0.4,0.2s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4c-0.2,0.1-0.5,0.3-1.1,0.4 + s-1.2,0.2-1.7,0.2c-1,0-1.7-0.3-2.3-0.8s-0.9-1.2-0.9-2c0-0.8,0.3-1.5,0.9-2.1s1.3-0.8,2.2-0.8c0.5,0,0.9,0.1,1.3,0.3 + s0.7,0.4,0.9,0.6c0.3,0.3,0.5,0.6,0.7,1.1c0.1,0.3,0.2,0.6,0.2,1V81.9z M63.6,80.7c-0.2-0.3-0.4-0.6-0.7-0.8s-0.7-0.3-1.1-0.3 + c-0.4,0-0.8,0.1-1.1,0.3s-0.5,0.4-0.7,0.8H63.6z"/> + <path id="XMLID_21_" class="st1" d="M69.7,76.3v6.4h1.4c0.3,0,0.5,0.1,0.6,0.2s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4 + s-0.3,0.2-0.6,0.2h-4.1c-0.3,0-0.5-0.1-0.6-0.2s-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2h1.4v-5.2h-1 + c-0.3,0-0.5-0.1-0.6-0.2s-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2H69.7z"/> + <path id="XMLID_23_" class="st1" d="M76.9,76.3v6.4h1.4c0.3,0,0.5,0.1,0.6,0.2s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4 + s-0.3,0.2-0.6,0.2h-4.1c-0.3,0-0.5-0.1-0.6-0.2s-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2h1.4v-5.2h-1 + c-0.3,0-0.5-0.1-0.6-0.2s-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2H76.9z"/> + <path id="XMLID_25_" class="st1" d="M84.6,83.9v-0.3c-0.3,0.2-0.6,0.3-1,0.4s-0.7,0.1-1,0.1c-0.6,0-1.2-0.2-1.6-0.5 + s-0.6-0.7-0.6-1.1c0-0.5,0.3-1,0.8-1.4s1.2-0.6,2.1-0.6c0.4,0,0.8,0,1.3,0.1v-0.3c0-0.2-0.1-0.3-0.2-0.4s-0.4-0.2-0.9-0.2 + c-0.4,0-0.8,0.1-1.4,0.2c-0.2,0.1-0.4,0.1-0.5,0.1c-0.2,0-0.3-0.1-0.4-0.2S81,79.5,81,79.3c0-0.1,0-0.2,0.1-0.3s0.1-0.1,0.2-0.2 + s0.2-0.1,0.4-0.2c0.3-0.1,0.6-0.1,0.9-0.2s0.6-0.1,0.8-0.1c0.7,0,1.3,0.2,1.7,0.5s0.6,0.8,0.6,1.3v2.5H86c0.3,0,0.5,0.1,0.6,0.2 + s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4s-0.3,0.2-0.6,0.2H84.6z M84.6,81.8c-0.5-0.1-0.9-0.1-1.3-0.1c-0.5,0-0.9,0.1-1.3,0.4 + c-0.2,0.2-0.3,0.3-0.3,0.5c0,0.1,0.1,0.2,0.2,0.3c0.2,0.1,0.5,0.2,0.8,0.2c0.3,0,0.6-0.1,1-0.2s0.7-0.3,1-0.5V81.8z"/> + <path id="XMLID_28_" class="st1" d="M90.5,78.6v0.8c0.5-0.4,0.9-0.6,1.2-0.7s0.6-0.2,0.8-0.2c0.4,0,0.8,0.1,1.1,0.4 + c0.3,0.2,0.4,0.4,0.4,0.6c0,0.2-0.1,0.3-0.2,0.4s-0.3,0.2-0.4,0.2c-0.1,0-0.3-0.1-0.5-0.2s-0.3-0.2-0.4-0.2 + c-0.2,0-0.4,0.1-0.8,0.3s-0.8,0.5-1.3,0.9v1.8h1.7c0.3,0,0.5,0.1,0.6,0.2s0.2,0.3,0.2,0.4c0,0.2-0.1,0.3-0.2,0.4 + s-0.3,0.2-0.6,0.2h-3.6c-0.3,0-0.5-0.1-0.6-0.2s-0.2-0.3-0.2-0.4c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2h0.7v-2.9h-0.4 + c-0.3,0-0.5-0.1-0.6-0.2S88,79.4,88,79.2c0-0.2,0.1-0.3,0.2-0.4s0.3-0.2,0.6-0.2H90.5z"/> + </g> + <path id="XMLID_318_" class="st2" d="M82,87.5c0,0.3-0.2,0.5-0.5,0.5h-63c-0.3,0-0.5-0.2-0.5-0.5l0,0c0-0.3,0.2-0.5,0.5-0.5h63 + C81.8,87,82,87.2,82,87.5L82,87.5z"/> + <path id="XMLID_319_" class="st2" d="M67,91.5c0,0.3-0.2,0.5-0.5,0.5h-34c-0.3,0-0.5-0.2-0.5-0.5l0,0c0-0.3,0.2-0.5,0.5-0.5h34 + C66.8,91,67,91.2,67,91.5L67,91.5z"/> + </g> + <g id="XMLID_273_"> + <path id="XMLID_274_" class="st2" d="M86.3,46c-5,1.3-10.4,3.4-15.8,6.9c-5.5,3.7-9.6,8-12.7,12.2c-2.2-3.3-4.5-6.6-6.7-10 + c5-1.5,10.4-3.8,15.7-7.4c5.3-3.6,9.5-7.7,12.8-11.8C81.9,39.4,84.1,42.7,86.3,46z"/> + <path id="XMLID_275_" class="st2" d="M47.7,55.5c-3-0.5-8.5-1.9-14.3-5.8c-5.9-3.9-9.3-8.6-10.9-11.2c-1.6,2.4-3.2,4.8-4.8,7.2 + c3.1,0.6,8.5,2.1,14.3,6c5.7,3.9,9.2,8.3,10.9,11C44.5,60.3,46.1,57.9,47.7,55.5z"/> + <path id="XMLID_281_" class="st2" d="M83,30.4c-3-0.5-8.5-1.9-14.2-5.7c-5.9-3.9-9.2-8.5-10.8-11.1c-1.6,2.4-3.2,4.8-4.8,7.2 + c3.1,0.6,8.5,2.1,14.2,5.9c5.7,3.8,9.1,8.3,10.8,10.9C79.8,35.2,81.4,32.8,83,30.4z"/> + <path id="XMLID_282_" class="st2" d="M51.1,22.3c-4.9,1.3-10.3,3.4-15.6,7c-5.4,3.7-9.4,8.1-12.3,12.4c-2.3-3.4-4.7-6.8-7-10.1 + c4.9-1.5,10.3-3.9,15.5-7.5c5.2-3.6,9.3-7.8,12.5-11.9C46.4,15.6,48.8,18.9,51.1,22.3z"/> + <g id="XMLID_283_"> + <g id="XMLID_302_"> + <circle id="XMLID_303_" class="st1" cx="84.8" cy="38.4" r="8.6"/> + </g> + <g id="XMLID_370_"> + <path id="XMLID_364_" class="st3" d="M92.7,38.4h-7.9v-7.9c0.6,2,1.2,4.1,1.9,6.1C88.6,37.2,90.7,37.8,92.7,38.4z"/> + <path id="XMLID_365_" class="st3" d="M76.8,38.4h7.9v-7.9c-0.6,2-1.2,4.1-1.9,6.1C80.9,37.2,78.8,37.8,76.8,38.4z"/> + <path id="XMLID_369_" class="st3" d="M92.7,38.4h-7.9v7.9c0.6-2,1.2-4.1,1.9-6.1C88.6,39.6,90.7,39,92.7,38.4z"/> + <path id="XMLID_366_" class="st3" d="M76.8,38.4h7.9v7.9c-0.6-2-1.2-4.1-1.9-6.1C80.9,39.6,78.8,39,76.8,38.4z"/> + </g> + </g> + <g id="XMLID_284_"> + <g id="XMLID_288_"> + <circle id="XMLID_289_" class="st1" cx="51.8" cy="15.4" r="8.6"/> + </g> + <g id="XMLID_285_"> + <path id="XMLID_287_" class="st3" d="M59.7,15.4h-7.9V7.5c0.6,2,1.2,4.1,1.9,6.1C55.6,14.2,57.7,14.8,59.7,15.4z"/> + <path id="XMLID_286_" class="st3" d="M43.8,15.4h7.9V7.5c-0.6,2-1.2,4.1-1.9,6.1C47.9,14.2,45.8,14.8,43.8,15.4z"/> + <path id="XMLID_290_" class="st3" d="M59.7,15.4h-7.9v7.9c0.6-2,1.2-4.1,1.9-6.1C55.6,16.6,57.7,16,59.7,15.4z"/> + <path id="XMLID_291_" class="st3" d="M43.8,15.4h7.9v7.9c-0.6-2-1.2-4.1-1.9-6.1C47.9,16.6,45.8,16,43.8,15.4z"/> + </g> + </g> + <g id="XMLID_292_"> + <g id="XMLID_296_"> + <circle id="XMLID_297_" class="st1" cx="50.8" cy="61.4" r="8.6"/> + </g> + <g id="XMLID_293_"> + <path id="XMLID_295_" class="st3" d="M58.7,61.4h-7.9v-7.9c0.6,2,1.2,4.1,1.9,6.1C54.6,60.2,56.7,60.8,58.7,61.4z"/> + <path id="XMLID_294_" class="st3" d="M42.8,61.4h7.9v-7.9c-0.6,2-1.2,4.1-1.9,6.1C46.9,60.2,44.8,60.8,42.8,61.4z"/> + <path id="XMLID_298_" class="st3" d="M58.7,61.4h-7.9v7.9c0.6-2,1.2-4.1,1.9-6.1C54.6,62.6,56.7,62,58.7,61.4z"/> + <path id="XMLID_299_" class="st3" d="M42.8,61.4h7.9v7.9c-0.6-2-1.2-4.1-1.9-6.1C46.9,62.6,44.8,62,42.8,61.4z"/> + </g> + </g> + <g id="XMLID_300_"> + <g id="XMLID_306_"> + <circle id="XMLID_307_" class="st1" cx="15.8" cy="38.4" r="8.6"/> + </g> + <g id="XMLID_301_"> + <path id="XMLID_305_" class="st3" d="M23.7,38.4h-7.9v-7.9c0.6,2,1.2,4.1,1.9,6.1C19.6,37.2,21.7,37.8,23.7,38.4z"/> + <path id="XMLID_304_" class="st3" d="M7.8,38.4h7.9v-7.9c-0.6,2-1.2,4.1-1.9,6.1C11.9,37.2,9.8,37.8,7.8,38.4z"/> + <path id="XMLID_308_" class="st3" d="M23.7,38.4h-7.9v7.9c0.6-2,1.2-4.1,1.9-6.1C19.6,39.6,21.7,39,23.7,38.4z"/> + <path id="XMLID_309_" class="st3" d="M7.8,38.4h7.9v7.9c-0.6-2-1.2-4.1-1.9-6.1C11.9,39.6,9.8,39,7.8,38.4z"/> + </g> + </g> + </g> +</g> +<g id="XMLID_1_"> +</g> +<g id="XMLID_31_"> +</g> +<g id="XMLID_32_"> +</g> +<g id="XMLID_33_"> +</g> +<g id="XMLID_34_"> +</g> +<g id="XMLID_35_"> +</g> +<g id="XMLID_36_"> +</g> +<g id="XMLID_37_"> +</g> +<g id="XMLID_38_"> +</g> +<g id="XMLID_39_"> +</g> +<g id="XMLID_40_"> +</g> +<g id="XMLID_41_"> +</g> +<g id="XMLID_42_"> +</g> +<g id="XMLID_43_"> +</g> +<g id="XMLID_44_"> +</g> +</svg> diff --git a/frontend/src/assets/svg/logo_no_text.svg b/frontend/src/assets/svg/logo_no_text.svg new file mode 100644 index 00000000..102b3781 --- /dev/null +++ b/frontend/src/assets/svg/logo_no_text.svg @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="1000px" height="700px" viewBox="0 0 1000 700" style="enable-background:new 0 0 1000 700;" xml:space="preserve"> +<style type="text/css"> + .st0{fill:#007EA7;} + .st1{fill:#00A8E8;} + .st2{fill:#FFFFFF;} +</style> +<g id="XMLID_273_"> + <path id="XMLID_274_" class="st0" d="M895.8,433.5C841.3,447.3,781,470.5,722,509.7c-60.6,40.3-106,88.3-140,134.5 + c-24.5-36.5-49-73-73.5-109.5c54.8-16.7,114.1-42.1,172.6-81.4C739.8,414,786,368.6,822.2,324C846.8,360.5,871.3,397,895.8,433.5z" + /> + <path id="XMLID_275_" class="st0" d="M471.8,537.7c-32.9-5.8-93.3-20.8-157.2-63.4c-64.8-43.3-102-94.3-119.9-122.6 + c-17.6,26.3-35.3,52.5-52.9,78.8c34,6.8,93.9,23.1,157.3,65.8c63.1,42.4,100.7,91.4,119.8,120.3C436.5,590.3,454.2,564,471.8,537.7 + z"/> + <path id="XMLID_281_" class="st0" d="M859.1,261.9c-32.7-5.7-92.8-20.4-156.3-62.8c-64.3-43-101.2-93.8-118.9-122 + c-17.6,26.3-35.3,52.5-52.9,78.8c33.8,6.7,93.4,22.8,156.4,65.1c62.6,42.1,99.9,90.9,118.9,119.7 + C823.9,314.5,841.5,288.2,859.1,261.9z"/> + <path id="XMLID_282_" class="st0" d="M509,173.3c-54.1,13.8-113.6,37.2-171.3,76.8c-59.3,40.7-103.1,89.3-135.6,136.1 + c-25.7-37.1-51.5-74.2-77.2-111.4c54.3-16.7,112.8-42.4,170-82.1C352.3,153,396.9,107,431.7,61.9C457.5,99,483.2,136.2,509,173.3z" + /> + <g id="XMLID_283_"> + <g id="XMLID_302_"> + <circle id="XMLID_303_" class="st1" cx="878.4" cy="350.1" r="94.3"/> + </g> + <g id="XMLID_370_"> + <path id="XMLID_364_" class="st2" d="M965.4,350.1h-86.9v-86.9c6.8,22.4,13.7,44.8,20.5,67.2 + C921.1,336.9,943.2,343.5,965.4,350.1z"/> + <path id="XMLID_365_" class="st2" d="M791.5,350.1h86.9v-86.9c-6.8,22.4-13.7,44.8-20.5,67.2 + C835.7,336.9,813.6,343.5,791.5,350.1z"/> + <path id="XMLID_369_" class="st2" d="M965.4,350.1h-86.9v86.9c6.8-22.4,13.7-44.8,20.5-67.2C921.1,363.3,943.2,356.7,965.4,350.1 + z"/> + <path id="XMLID_366_" class="st2" d="M791.5,350.1h86.9v86.9c-6.8-22.4-13.7-44.8-20.5-67.2C835.7,363.3,813.6,356.7,791.5,350.1 + z"/> + </g> + </g> + <g id="XMLID_284_"> + <g id="XMLID_288_"> + <circle id="XMLID_289_" class="st1" cx="516" cy="97.5" r="94.3"/> + </g> + <g id="XMLID_285_"> + <path id="XMLID_287_" class="st2" d="M602.9,97.5H516V10.6c6.8,22.4,13.7,44.8,20.5,67.2C558.7,84.3,580.8,90.9,602.9,97.5z"/> + <path id="XMLID_286_" class="st2" d="M429,97.5H516V10.6c-6.8,22.4-13.7,44.8-20.5,67.2C473.3,84.3,451.2,90.9,429,97.5z"/> + <path id="XMLID_290_" class="st2" d="M602.9,97.5H516v86.9c6.8-22.4,13.7-44.8,20.5-67.2C558.7,110.7,580.8,104.1,602.9,97.5z"/> + <path id="XMLID_291_" class="st2" d="M429,97.5H516v86.9c-6.8-22.4-13.7-44.8-20.5-67.2C473.3,110.7,451.2,104.1,429,97.5z"/> + </g> + </g> + <g id="XMLID_292_"> + <g id="XMLID_296_"> + <circle id="XMLID_297_" class="st1" cx="505" cy="602.7" r="94.3"/> + </g> + <g id="XMLID_293_"> + <path id="XMLID_295_" class="st2" d="M591.9,602.7H505v-86.9c6.8,22.4,13.7,44.8,20.5,67.2C547.7,589.5,569.8,596.1,591.9,602.7z + "/> + <path id="XMLID_294_" class="st2" d="M418,602.7H505v-86.9c-6.8,22.4-13.7,44.8-20.5,67.2C462.3,589.5,440.2,596.1,418,602.7z"/> + <path id="XMLID_298_" class="st2" d="M591.9,602.7H505v86.9c6.8-22.4,13.7-44.8,20.5-67.2C547.7,615.9,569.8,609.3,591.9,602.7z" + /> + <path id="XMLID_299_" class="st2" d="M418,602.7H505v86.9c-6.8-22.4-13.7-44.8-20.5-67.2C462.3,615.9,440.2,609.3,418,602.7z"/> + </g> + </g> + <g id="XMLID_300_"> + <g id="XMLID_306_"> + <circle id="XMLID_307_" class="st1" cx="120.6" cy="350.1" r="94.3"/> + </g> + <g id="XMLID_301_"> + <path id="XMLID_305_" class="st2" d="M207.5,350.1h-86.9v-86.9c6.8,22.4,13.7,44.8,20.5,67.2 + C163.3,336.9,185.4,343.5,207.5,350.1z"/> + <path id="XMLID_304_" class="st2" d="M33.7,350.1h86.9v-86.9c-6.8,22.4-13.7,44.8-20.5,67.2C77.9,336.9,55.8,343.5,33.7,350.1z" + /> + <path id="XMLID_308_" class="st2" d="M207.5,350.1h-86.9v86.9c6.8-22.4,13.7-44.8,20.5-67.2C163.3,363.3,185.4,356.7,207.5,350.1 + z"/> + <path id="XMLID_309_" class="st2" d="M33.7,350.1h86.9v86.9c-6.8-22.4-13.7-44.8-20.5-67.2C77.9,363.3,55.8,356.7,33.7,350.1z"/> + </g> + </g> +</g> +<g id="XMLID_1_"> +</g> +<g id="XMLID_2_"> +</g> +<g id="XMLID_3_"> +</g> +<g id="XMLID_4_"> +</g> +<g id="XMLID_5_"> +</g> +<g id="XMLID_6_"> +</g> +<g id="XMLID_7_"> +</g> +<g id="XMLID_8_"> +</g> +<g id="XMLID_9_"> +</g> +<g id="XMLID_10_"> +</g> +<g id="XMLID_11_"> +</g> +<g id="XMLID_12_"> +</g> +<g id="XMLID_13_"> +</g> +<g id="XMLID_14_"> +</g> +<g id="XMLID_15_"> +</g> +</svg> diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 8997c10e..d37ab6f1 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1,5 +1,3 @@ -/* You can add global styles to this file, and also import other style files - -html, body { height: 100%; } -body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } -*/
\ No newline at end of file +body { + background-image: url('/assets/images/add_model_background.jpg'); +}
\ No newline at end of file |