Telerik Forums
UI for ASP.NET Core Forum
1 answer
142 views

Hi!

I have this scenario where I want to forward the current model's sub-object as a model to a DisplayTemplate within the Grid's ClientDetailTemplate: Checkout the first Tab of the TabStrip in the ClientDetailTemplate

Main View:

@(Html.Kendo().Grid(Model)
      .Name("gridLetters")
      .Columns(columns =>
      {
	      columns.Bound(p => p.Id).ClientTemplate("<a href='" + Url.Action("View", "Letter", new { letterId = "#:Id#" }) + "'>#=Id#</i></a>");
	      columns.Bound(p => p.Subject);
	      columns.Bound(p => p.CompanyName).Title("Company");
	      columns.Bound(p => p.BrandName).Title("Brand");
	      columns.Bound(p => p.Location.Name).Title("Location");
	      columns.Bound(p => p.LetterType.Name).Title("RL Type");	      
      })
      .ToolBar(toolbar =>
      {
	      toolbar.Search();
      })
      .Sortable()
      .Navigatable()
      .Resizable(r => r.Columns(true))
      .Pageable(pageable => pageable
	                .Refresh(true)
	                .PageSizes(true)
	                .ButtonCount(5))
      .Filterable()
      .Scrollable()
      .ClientDetailTemplateId("detailTemplate")
      .HtmlAttributes(new { style = "height:430px;" })
      .DataSource(dataSource => dataSource
	                  .Ajax()
	                  .Sort(x => x.Add("Id").Descending())
	                  .PageSize(20)
	                  .ServerOperation(false)
	  ))

@section Scripts {
	<script id="detailTemplate" type="text/kendo-tmpl">
	    @(Html.Kendo().TabStrip()
	          .Name("tabStrip_#=Id#")
	          .SelectedIndex(0)
	          .Animation(animation => animation.Open(open => open.Fade(FadeDirection.In)))
	          .Items(items =>
	          {
		          items.Add().Text("History").Content(@<text>@Html.DisplayFor(m => m.History, "LetterHistory")</text>);
		          items.Add().Text("Details").Content("");
	          }).ToClientTemplate())
	</script>
}

Above, the model for the main grid is an IList of my objects. Each object has a History property of type

BusApp.Domain.Models.BusinessObjects.LetterActionHistoryFlatModel

I need to get that property passed on as a model for the DisplayFor

LetterHistory.cshtml

@model IList<BusApp.Domain.Models.BusinessObjects.LetterActionHistoryFlatModel>

@(Html.Kendo().Grid(Model)
      .Name("gridLetterHistory")
      .Columns(columns =>
      {
	      columns.Bound(p => p.ActionStamp).Title("Stamp").Format("{0:dd-MMM-yyyy}");
	      columns.Bound(p => p.LetterActionName).Title("Action");
	      columns.Bound(p => p.ActionTakenByName).Title("Taken By");
      })
      .Navigatable()
      .Resizable(r=>r.Columns(true))
      .Pageable(pageable => pageable
	                .Refresh(true)
	                .PageSizes(true)
	                .ButtonCount(10))
      .Filterable()
      .Scrollable()
      .DataSource(dataSource => dataSource
	                  .Ajax()
	                  .Sort(x => x.Add("ActionStamp").Descending())
	                  .PageSize(20)
	                  .ServerOperation(false)
	  )
	)

Regrds.

Mihaela
Telerik team
 answered on 14 Jul 2022
1 answer
194 views

The aspnet core binaries looked like this with the previous release. See below.

Now, it appears the net6.0 is gone.Why go backwards to netstandard2.0? Where was this explained? I feel like upgrading is a huge downgrade so I should NOT do it.

We were told this 2 years ago - https://visualstudiomagazine.com/articles/2020/09/16/net-standard-future.aspx .

Stoyan
Telerik team
 answered on 11 Jul 2022
1 answer
168 views

hello, 
what is the general pattern to include a grid inside another Grid's DetailTemplate? I have a heriarchical collections I need to populate. I don't see other way than declaring a script inside the template's script tag, but that is invalid html.

Momchil
Telerik team
 answered on 08 Jul 2022
0 answers
120 views

I have defined in my cshtml the charts with this

  • the model : Html.Kendo().Chart<MyModelClass>()
  • the datasource: 
.DataSource(ds =>
        {
            ds.Read(read => read.Action(Model.ActionMethod, Model.ControllerActionMethod).Type(HttpVerbs.Post));
            if (Model.SeriesGrouped)
            {
                //to create data groups
                ds.Group(group => group.Add(model => model.Group)).Sort(s => s.Add(m => m.Order));
            }
        })

 

The class MyModelClass is like this

public class MyModelClass
    {
        public string Group { get; set; }
        public string Category { get; set; }
        public DateTime Order { get; set; } 
      
    }

 

When all groups of my datasource has values for all axies thats work fine, by example if my datasource result is like this:

{Group:"A", Order:"01/2021", value="1000"}
{Group:"A", Order:"02/2021", value="1200"}
{Group:"A", Order:"03/2021", value="1100"}
{Group:"B", Order:"01/2021", value="2000"}
{Group:"B", Order:"02/2021", value="2200"}
{Group:"B", Order:"03/2021", value="2100"}

The chart displayed the axies in a good way, I mean the period is ordered  by date (first jan, then feb and las march) "   01/2021         02/2021            03/2021"

But when one group of the datasource result doesn't have values in a period the beahavior is diferent, the order of axies will be created depending of order of groupin that telerik chart create.  I will explain:

My datasource result is (i remove the first value to period 01/2021):

{Group:"A", Order:"02/2021", value="1200"}
{Group:"A", Order:"03/2021", value="1100"}
{Group:"B", Order:"01/2021", value="2000"}
{Group:"B", Order:"02/2021", value="2200"}
{Group:"B", Order:"03/2021", value="2100"}

then the axies on chart is displayed like this (firs feb, then march, then jan)  " 02/2021    03/2021   01/2021 "

so the result final in my implementation is like this

this problem doesn't happen when all groups has data for all axies, the field used for create the axies is a datetime.

 

 

Gabriel
Top achievements
Rank 1
 updated question on 08 Jul 2022
1 answer
500 views

Hope I'm asking this question in the right place.

I have this working:

    @(Html.Kendo().Menu()
            .Name("QmsMenu")
            .DataTextField("Title")
            .DataSource(ds => ds.Read("Menu_Read", "Menu")
            .Model(model => model.Children("MenuItems")))
    )

Reference: https://demos.telerik.com/aspnet-core/menu/remote-data-binding

However, I can't figure out how to dynamically add links to each of the items in a Menu binding to remote data.

The payload returned from the server is providing the controller name and controller action method name for each of the menu items. I would like to add these values to the code above enabling a user to click on a menu link and be taken to a different page in the web application.

Would appreciate some help regarding this.

 

Aleksandar
Telerik team
 answered on 07 Jul 2022
1 answer
133 views

Hi I have a requirement to show a popup when a card is moved or dropped to  another column say working to done. in the popup if i say no then the card has to be moved back to where it was before.  Basically can i undo the movement after validation or can i move the object without manual intervention. Does there are methods to move the card from one column to another thru Javascript?

 

Thanks!

Venu

Mihaela
Telerik team
 answered on 06 Jul 2022
0 answers
617 views

Using a controller based off of the sample code and the chunked upload configuration, I find that in local testing I eventually end up with an IO exception claiming another process is trying to access the file when I'm uploading larger files.

I suspect I am somehow having the AppendToFile() call run into the previous AppendToFile() call before it is done syncing to disk or something, but have no proof.

In addition, when the fallback Save() call is used (if I forget to set a chunk size) the files turn out corrupt, being only partially uploaded. 

AV is disabled for the relevant directories and my Git services are not running, and so I'm not sure what else could be accessing the file. Is there a way to solve this issue?

I can go and try using the vanilla version of the sample code if I must but feel that there may be something else going on here regardless.

Thank you!

My Controller:


using Csla;
using Finbuckle.MultiTenant;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using Newtonsoft.Json;
using Portal.Library;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;

namespace WebUI.Controllers
{
    public class FileStore : Csla.Web.Mvc.Controller
    {
        private string UploadBasePath = "wwwroot\\uploads";
        private string UploadStoragePath = "wwwroot\\storage";

        public WebUI.TenantInfo? TenantInfo { get; private set; }

        public FileStore(IMultiTenantContextAccessor<WebUI.TenantInfo> mtaccessor)
        {
            TenantInfo = mtaccessor.MultiTenantContext.TenantInfo;
        }

        public class ChunkMetaData
        {
            public string UploadUid { get; set; }
            public string FileName { get; set; }
            public string RelativePath { get; set; }
            public string ContentType { get; set; }
            public long ChunkIndex { get; set; }
            public long TotalChunks { get; set; }
            public long TotalFileSize { get; set; }
        }

        public class FileResult
        {
            // Because the chunks are sent in a specific order,
            // the server is expected to send back a response
            // with the meta data of the chunk that is uploaded.
            public bool uploaded { get; set; }

            public string fileUid { get; set; }

            public string fileStorageGuid { get; set; } // internal GUID in DB per file not per session

            public string selector { get; set; } // because the UID isn't used or sent for the fallback Save()
        }

        public void AppendToFile(string fullPath, IFormFile content)
        {
            try
            {
                using (FileStream stream = new(fullPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
                {
                    content.CopyTo(stream);
                }
            }
            catch (IOException ex)
            {
                throw ex;
            }
        }

        public ActionResult ChunkSave(IEnumerable<IFormFile> files, string metaData)
        {
            if (metaData == null)
            {
                return Save(files);
            }

            MemoryStream ms = new(Encoding.UTF8.GetBytes(metaData));

            JsonSerializer serializer = new();
            ChunkMetaData chunkData;
            using (StreamReader streamReader = new(ms))
            {
                chunkData = (ChunkMetaData)serializer.Deserialize(streamReader, typeof(ChunkMetaData));
            }

            string path = Path.Combine(UploadBasePath, chunkData.FileName);
            // The Name of the Upload component is "files".
            if (files != null)
            {
                foreach (var file in files)
                {
                    AppendToFile(path, file);
                }
            }

            var isUploaded = chunkData.TotalChunks - 1 <= chunkData.ChunkIndex;
            FileResult fileBlob = new()
            {
                uploaded = isUploaded,
                fileUid = chunkData.UploadUid
            };

            if (isUploaded)
            {
                var bytes = Encoding.UTF8.GetBytes(chunkData.FileName);

                fileBlob.fileStorageGuid = CatalogAndMoveFileAsync(chunkData.FileName, path).ToString();
                fileBlob.selector = Convert.ToBase64String(bytes);
            }

            return Json(fileBlob);
        }

        private Guid CatalogAndMoveFileAsync(string FileName, string FilePath)
        {
            //sanity
            if (System.IO.File.Exists(FilePath))
            {
                // step 1 - generate guid in files table by inserting the requisite information
                var _user = Csla.ApplicationContext.User as ClaimsPrincipal;
                int userID = int.Parse(_user.Claims.FirstOrDefault(S => S.Type == "UserID").Value.ToString());

                var finfo = new FileInfo(FilePath);

                var obj = DataPortal.Create<FileStorage>();
                obj.UserID = userID;
                obj.Extension = Path.GetExtension(FileName);
                obj.OrigName = FileName;
                obj.KBSize = finfo.Length / 1024;
                obj.SiteID = TenantInfo.SiteID;

                // TODO: revisit and see if SaveObjectAsync updates our object v6
                // use the object's own save function to update the object
                obj = obj.Save();

                // do we have to re-fetch or is the object guid updated automatically?
                if (obj.ID != Guid.Empty)
                {
                    // step 2 - determine new name, location, move to it
                    string StorageSubfolders = Path.Combine(UploadStoragePath, obj.RelPath);

                    try
                    {
                        System.IO.Directory.CreateDirectory(StorageSubfolders); // so glad this func does it in one go

                        var newFilePath = Path.Combine(StorageSubfolders, obj.InternalName);
                        System.IO.File.Move(FilePath, newFilePath);

                        if (System.IO.File.Exists(newFilePath))
                        {
                            // step 3 - return the guid
                            return obj.ID;
                        }
                    }
                    catch (Exception ex)
                    {
                        if (obj.ID != Guid.Empty)
                        {
                            DataPortal.Delete<FileStorage>(obj.ID, userID);
                        }

                        throw new FileNotFoundException("FileStorage moving file", ex);
                    }
                }
            }

            return Guid.Empty;
        }

        public ActionResult RemoveAsync(string[] fileNames, string[] FileStorageUIDs)
        {
            // we don't actually care about fileNames, telerik always sends *something* related to that though
            // we rely on our file UID(s) that were sent back and hopefully captured in the onSuccess event

            // The parameter of the Remove action must be called "fileNames"
            if (FileStorageUIDs.Length > 0)
            {
                foreach (var fUID in FileStorageUIDs)
                {
                    // get the current UID to be 100% sure we're deleting only our own file
                    var _user = Csla.ApplicationContext.User as ClaimsPrincipal;
                    int userID = int.Parse(_user.Claims.FirstOrDefault(S => S.Type == "UserID").Value.ToString());

                    var obj = DataPortal.Fetch<FileStorage>(new Guid(fUID), userID);

                    if (obj.ID != Guid.Empty)
                    {
                        var basePath = Path.Combine(UploadStoragePath, obj.RelPath);
                        var physicalPath = Path.Combine(basePath, obj.InternalName);
                        if (System.IO.File.Exists(physicalPath))
                        {
                            System.IO.File.Delete(physicalPath);

                           //TODO: maybe try/catch?
                            if (Directory.EnumerateFiles(basePath).Any() == false)
                            {
                                Directory.Delete(basePath, true);
                            }

                            // we already fetched with UID
                            DataPortal.Delete<FileStorage>(obj.ID, userID);
                        }
                    }
                }
            }

            // Return an empty string to signify success
            return Content("");
        }

        // TODO: while this method fully works, for whatever reason it corrupts files when we fall back to it
        public ActionResult Save(IEnumerable<IFormFile> files)
        {
            List<string> fileNames = new();
            List<FileResult> results = new();
            
            // The Name of the Upload component is "files".
            if (files != null)
            {
                foreach (var file in files)
                {
                    var fileContent = ContentDispositionHeaderValue.Parse(file.ContentDisposition);

                    // Some browsers send file names with full path.
                    // The demo is interested only in the file name.
                    var fileName = Path.GetFileName(fileContent.FileName.ToString().Trim('"'));
                    var physicalPath = Path.Combine(UploadBasePath, fileName);

                    using var fileStream = new FileStream(physicalPath, FileMode.Create);
                    file.CopyToAsync(fileStream);

                    fileNames.Add(fileName);
                }

                // since we can't delete while in the scope of the loop there, we have to do a second one
                foreach (var fileName in fileNames)
                {
                    var physicalPath = Path.Combine(UploadBasePath, fileName);

                    var fileStorageGuid = CatalogAndMoveFileAsync(fileName, physicalPath);
                    string guidString = fileStorageGuid.ToString();

                    var bytes = Encoding.UTF8.GetBytes(fileName);

                    results.Add(new FileResult()
                    {
                        uploaded = true,
                        fileStorageGuid = guidString,
                        selector = Convert.ToBase64String(bytes)
                    });
                }

                return Json(results);
            }

            return Content("");
        }

        public async Task GetFile(string FileStorageUID)
        {
            var _user = Csla.ApplicationContext.User as ClaimsPrincipal;
            int userID = int.Parse(_user.Claims.FirstOrDefault(S => S.Type == "UserID").Value.ToString());

            // TODO: maybe add a func to the user dal to fetch hierarchy?
            // TODO: check role here, if UID doesn't match, don't pass userID

            var obj = await DataPortal.FetchAsync<FileStorage>(new Guid(FileStorageUID), 0); // uid is second param if needed

            if (obj.ID != Guid.Empty)
            {
                var basePath = Path.Combine(UploadStoragePath, obj.RelPath);
                var physicalPath = Path.Combine(basePath, obj.InternalName);
                if (System.IO.File.Exists(physicalPath))
                {
                    var provider = new FileExtensionContentTypeProvider();
                    if (!provider.TryGetContentType(physicalPath, out string contentType))
                    {
                        contentType = "application/octet-stream";
                    }

                    Response.Clear();
                    Response.Headers.Add("Content-Disposition", "inline;filename=" + obj.OrigName);
                    Response.ContentType = contentType;

                    await Response.SendFileAsync(physicalPath);
                }
            }
        }
    }
}

My View:


                        @(Html.Kendo().Upload()
                            .Name("files")
                            .Async(a => a
                                .Save("ChunkSave", "FileStore")
                                .Remove("Remove", "FileStore")
                                .AutoUpload(true)
                                .ChunkSize(10240) 
                            )
                            .Validation(validation => validation
                                //.AllowedExtensions(new string[] { ".gif", ".jpg", ".png" })
                                .MaxFileSize(200000000) // 200 megs
                               //.MinFileSize(512)
                            )
                            .Events(events => events
                                .Success("onSuccess")
                                .Remove("onRemove")
                                .Upload("onUpload")
                            ).Multiple(false) // one file and one file only
                        ) 

Matt
Top achievements
Rank 1
 updated question on 05 Jul 2022
0 answers
125 views

Hi!

I have the following setup:

CsHtml:

	<div class="row mt-3">
		<div class="col-lg-4">
			@(Html.Kendo().DropDownListFor(m => m.CategoryHeadId)
			      .Size(ComponentSize.Medium)
				  .Rounded(Rounded.Medium)
				  .FillMode(FillMode.Solid)
				  .OptionLabel("Select head category...")
				  .HtmlAttributes(new { style = "width: 100%" })
				  .DataTextField("Name")
				  .DataValueField("Id")
				  .DataSource(source =>
				  {
					  source.Read(read =>
					  {
						  read.Action("GetLookupCategoriesHead", "Api");
					  });
				  })
				)
		</div>
		<div class="col-lg-4">
			@(Html.Kendo().DropDownListFor(m => m.CategoryMainId)
			      .Size(ComponentSize.Medium)
				.Rounded(Rounded.Medium)
				.FillMode(FillMode.Solid)
				.OptionLabel("Select main category...")
				.HtmlAttributes(new { style = "width: 100%" })
				.DataTextField("Name")
				.DataValueField("Id")
				.DataSource(source =>
				{
					source.Read(read =>
					{
						read.Action("GetLookupCategoriesMain", "Api")
						    .Data("filterMainCategories");
					})
					.ServerFiltering(true);
				})
				.Enable(false)
				.AutoBind(false)
				.CascadeFrom("CategoryHeadId")
				)
		</div>
		<div class="col-lg-4">
			@(Html.Kendo().DropDownListFor(m => m.CategorySubId)
				.Size(ComponentSize.Medium)
				.Rounded(Rounded.Medium)
				.FillMode(FillMode.Solid)
				.OptionLabel("Select sub-category...")
				.HtmlAttributes(new { style = "width: 100%" })
				.DataTextField("Name")
				.DataValueField("Id")
				.DataSource(source =>
				{
					source.Read(read =>
					{
						read.Action("GetLookupCategoriesSub", "Api")
						    .Data("filterSubCategories");
					})
					.ServerFiltering(true);
				})
				.Enable(false)
				.AutoBind(false)
				.CascadeFrom("CategoryMainId")
				)
		</div>
	</div>

Script:

@section Scripts {
	<script>
		function filterMainCategories() {
			return {
				headId: $("#CategoryHeadId").val()
			};
		}

		function filterSubCategories() {
			return {
				headId: $("#CategoryHeadId").val(),
				mainId: $("#CategoryMainId").val()
			};
		}
	</script>
}

Controller:

    public async Task<JsonResult> GetLookupCategoriesHead()
    {
        var result = await serviceLookUps.GetAllCategoriesHeadAsync();
        return new JsonResult(result);
    }

    public async Task<JsonResult> GetLookupCategoriesMain(int headId)
    {
        var result = await serviceLookUps.GetAllCategoriesMainAsync(headId);
        return new JsonResult(result);
    }

    public async Task<JsonResult> GetLookupCategoriesSub(int headId, int mainId)
    {
        var result = await serviceLookUps.GetAllCategoriesSubAsync(headId, mainId);
        return new JsonResult(result);
    }

Model:

[DebuggerDisplay($"{nameof(Id)}: {{{nameof(Id)},nq}}, {nameof(Name)}: {{{nameof(Name)}}}, {nameof(Active)}: {{{nameof(Active)}}}")]
public class LookupCategoryHeadModel
{
    public int    Id     { get; set; }
    public string Name   { get; set; } = string.Empty;
    public bool   Active { get; set; }
}

[DebuggerDisplay($"{nameof(Id)}: {{{nameof(Id)},nq}}, {nameof(Name)}: {{{nameof(Name)}}}, {nameof(Active)}: {{{nameof(Active)}}}")]
public class LookupCategoryMainModel
{
    public int    Id     { get; set; }
    public int    HeadId { get; set; }
    public string Name   { get; set; } = string.Empty;
    public bool   Active { get; set; }
}

[DebuggerDisplay($"{nameof(Id)}: {{{nameof(Id)},nq}}, {nameof(Name)}: {{{nameof(Name)}}}, {nameof(Active)}: {{{nameof(Active)}}}")]
public class LookupCategorySubModel
{
    public int    Id     { get; set; }
    public int    HeadId { get; set; }
    public int    MainId { get; set; }
    public string Name   { get; set; } = string.Empty;
    public bool   Active { get; set; }
}
Issue:

I have tested the API methods separately through tests and ensured they are returning values. The only issue is, when I make a selection in the second dropdown, on the API, the second parameter is received as 0 instead of a correct selected value. This doesn't cause any console errors so the UI continues to work. I can cascade from dropdown one to dropdown two but dropdown two sends a 0 for mainId from its filtering operation in filterSubCategories()

DoomerDGR8
Top achievements
Rank 2
Iron
Iron
Iron
 asked on 04 Jul 2022
1 answer
102 views

In Kendo UI for ASP.Net MVC, you could bind a grid directly to a DataTable, as in

@Html.Kendo().Grid(MyDataTable)...

where MyDataTable is a DataTable object populated with data. 

However, using the ASP.Net Core kendo libraries, there is no support for this.

Why not? Are Telerik planning to add this?

Thanks.

Aleksandar
Telerik team
 answered on 01 Jul 2022
1 answer
874 views

So I just created a .NET 6 ASP.NET Core Web App (Razor Pages), added all telerik stuff according to the instructions (NuGet Packages, scripts and styles in _Layout.cshtml, @addTagHelper-s in _ViewImports.cshtml), and HTMLHelpers are rendering correctly. However, when I provide a data source to the DropDownList or ComboBox, all options show up as undefined.

Telerik version: 2022.2.621.   Bootstrap: V5.


Here's the code for DropDownList:

Index.cshtml

@page
@model IndexModel

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@Html.AntiForgeryToken()
@{
    ViewData["Title"] = "Home page";
}

--------------------------------------------------------------------------------

 

 

<div class="text-center"> <h1 class="display-4">Welcome</h1> @(Html.Kendo().DropDownList() .Name("MyDropdownn") .DataTextField("Text") .DataValueField("Value") .HtmlAttributes(new { style = "width:300px;" }) .AutoBind(false) .Filter(FilterType.Contains) .DataSource(ds => ds .Custom() .Transport(transport => transport .Read(r => r .Url("/Index?handler=Sports") )) .ServerFiltering(false) ) ) </div>

 

Index.cshtml.cs

        public JsonResult OnGetSports()
        {
            var allSports1 = db.Sports.Where(x => x.SportID > 0).Select(x => new DropDownModel
            {
                Text = x.Name,
                Value = x.SportID 
            }).ToList();

            return new JsonResult(allSports1);
        }

Proof that the data isn't empty/undefined

DropDownModel.cs (DropDownModel class)

    public class DropDownModel
    {
        public int Value { get; set; }
        public string Text { get; set; } = String.Empty;
    }

 

I've tried restarting everything, double-checking everything but nothing helped. Is there a bug with this version of telerik, is something incompatible with ,NET 6 or Bootstrap 5? Or am I doing something wrong? 

Alexander
Telerik team
 answered on 29 Jun 2022
Narrow your results
Selected tags
Tags
+? more
Top users last month
Anislav
Top achievements
Rank 6
Silver
Bronze
Bronze
Jianxian
Top achievements
Rank 1
Iron
Marco
Top achievements
Rank 3
Iron
Iron
Iron
Jim
Top achievements
Rank 2
Iron
Iron
Nurik
Top achievements
Rank 2
Iron
Iron
Want to show your ninja superpower to fellow developers?
Top users last month
Anislav
Top achievements
Rank 6
Silver
Bronze
Bronze
Jianxian
Top achievements
Rank 1
Iron
Marco
Top achievements
Rank 3
Iron
Iron
Iron
Jim
Top achievements
Rank 2
Iron
Iron
Nurik
Top achievements
Rank 2
Iron
Iron
Want to show your ninja superpower to fellow developers?
Want to show your ninja superpower to fellow developers?