using System.Collections.Generic; using System.IO.Compression; // using System.Net; using System; using System.Linq; using UMC.SshNet; using UMC.Web; using UMC.Web.UI; using UMC.Net; namespace UMC.ITME.Activities; [Apiumc("Proxy", "SSH", Auth = WebAuthType.User)] class SiteSSHActivities : Web.WebActivity { public static void SSHLog(Entities.Device device, string username, params string[] logs) { var file = UMC.Data.Reflection.ConfigPath($"Static\\log\\WebSSH\\{device.Ip}\\{username}.log"); if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(file))) { System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); } System.IO.File.AppendAllLines(file, logs); } public override void ProcessActivity(WebRequest request, WebResponse response) { var Key = this.AsyncDialog("Key", g => { var form = request.SendValues ?? new WebMeta(); var limit = form["limit"] ?? "none"; var keys = new List(); var user = this.Context.Token.Identity(); var searcher = UMC.Data.HotCache.Search(); var Keyword = form["Keyword"]; if (String.IsNullOrEmpty(Keyword) == false) { searcher.Or().Like(new Entities.Device { Caption = Keyword, Ip = Keyword, Username = Keyword, Desc = Keyword }); } switch (limit) { case "PC": { var sts = new System.Data.DataTable(); sts.Columns.Add("id"); sts.Columns.Add("ip"); sts.Columns.Add("caption"); sts.Columns.Add("user"); sts.Columns.Add("port"); sts.Columns.Add("type"); var ds = searcher.Query(new Entities.Device(), false, 0, 500, out var index); Utility.Each(ds, r => keys.Add($"WebSSH/{r.Id}")); var auths = UMC.Security.AuthManager.Authorization(user, 0, keys.ToArray()); var count = 0; int i = 0; foreach (var d in ds) { if (auths[i].Item1 > 0) { count++; sts.Rows.Add(d.Id, d.Ip, d.Caption, d.Username, d.Port, d.Type); } i++; } var rdata = new WebMeta().Put("data", sts); if (count == 0) { if (String.IsNullOrEmpty(Keyword)) { rdata.Put("msg", "未有可访问的设备"); } else { rdata.Put("msg", $"未搜索到“{Keyword}”相关可访问的设备"); } } var lic = UMC.Data.License.GetLicense("WebSSH", 5); Data.Caches.ICacheSet cacheSet = UMC.Data.HotCache.Cache(); if ((cacheSet.Count > lic.Quantity && lic.Quantity > 0) || (lic.ExpireTime > 0 && lic.ExpireTime < Utility.TimeSpan())) { response.Redirect("System", "License", new Web.UIConfirmDialog("设备数量超限,请保持合规.") { Title = "版权授权", DefaultValue = "WebSSH" }, false); } this.Context.Send("WebSSH", request.IsMaster ? rdata.Put("IsMaster", true) : rdata, true); } break; default: { var title = UITitle.Create(); title.Title = "我的设备"; var ds = searcher.Query(new Entities.Device(), false, 0, 500, out var index); Utility.Each(ds, r => keys.Add($"WebSSH/{r.Id}")); var auths = UMC.Security.AuthManager.Authorization(user, 0, keys.ToArray()); if (request.IsMaster) title.Right(new UIEventText().Icon('\ue907').Click(new UIClick(request, g, "Create"))); var ui = UISection.Create(title); var webr = UMC.Data.WebResource.Instance(); var count = 0; int i = 0; foreach (var d in ds) { if (auths[i].Item1 > 0) { var item = new UIIconNameDesc.Item('\uf17c', d.Caption, $"{d.Username}@{d.Ip}"); count++; UIIconNameDesc uIIcon = new UIIconNameDesc(item); item.Color(0x111); item.Click(new UIClick(request, g, d.Id.ToString())); if (request.IsMaster) { ui.Delete(uIIcon, new UIEventText("删除").Click(new UIClick(request, g, d.Id.ToString(), "Model", "Remove"))); } else { ui.Add(uIIcon); } } i++; } if (count == 0) { if (String.IsNullOrEmpty(Keyword)) { var desc = new UIDesc("未有可访问的设备"); desc.Put("icon", "\uf016").Format("desc", "{icon}\n{desc}"); desc.Style.Align(1).Color(0xaaa).Padding(20, 20).BgColor(0xfff).Size(12).Name("icon", new UIStyle().Font("wdk").Size(60)); ui.Add(desc); } else { var desc = new UIDesc($"未搜索到“{Keyword}”相关可访问的设备"); desc.Put("icon", "\uf016").Format("desc", "{icon}\n{desc}"); desc.Style.Align(1).Color(0xaaa).Padding(20, 20).BgColor(0xfff).Size(12).Name("icon", new UIStyle().Font("wdk").Size(60)); ui.Add(desc); } } ui.SendTo(this.Context, true, $"{request.Model}.{request.Command}"); } break; } return this.DialogValue("none"); }); switch (Key) { case "Create": Create(); break; } var device = UMC.Data.HotCache.Get(new Entities.Device { Id = Utility.IntParse(Key, 0) }); var model = this.AsyncDialog("Model", g => { int start = UMC.Data.Utility.IntParse(request.SendValues?["start"] as string, 0); var nextKey = request.SendValues?["NextKey"] ?? "Header"; UISection ui = null; UISection pathUI = null; if (start == 0 && String.Equals(nextKey, "Header")) { ui = UISection.Create(new UITitle("设备信息")); ui.AddCell("设备名称", device.Caption, new UIClick(request, g, "Editer")); var cell = UI.UI("登录地址", $"{device.Ip}:{device.Port}"); if (request.IsMaster && request.IsApp == false) { ui.Delete(cell, new UIEventText("删除").Click(new UIClick(request, g, "Remove"))); } else { ui.Add(cell); } ui.AddCell("用户名", device.Username); if (request.IsMaster) { if (String.IsNullOrEmpty(device.Desc) == false) { ui.AddCell("描述", device.Desc); } ui.AddCell("设置权限", new UIClick("Settings", "AuthKey", new WebMeta().Put("Key", $"WebSSH/{device.Id}"))); } pathUI = ui.NewSection(); } else { ui = pathUI = UISection.Create(); } pathUI.Key = "Path"; var path = this.AsyncDialog("Path", "/"); var password = UMC.Data.DataFactory.Instance().Password(SiteConfig.MD5Key(device.Ip, device.Username)); try { using (var sftp = new SftpClient(new PasswordConnectionInfo(device.Ip, device.Port ?? 22, device.Username, password))) { sftp.Connect(); var sftpFiles = sftp.ListDirectory(path).OrderByDescending(r => r.IsDirectory).Where(f => f.Name != "." && f.Name != ".."); if (String.Equals(path, "/")) { pathUI.AddCell('\uf0e8', "根目录", "/"); } else { var name = path.LastIndexOf('/', path.Length - 1); var parentPath = path.Substring(0, name); var click = request.IsApp ? UIClick.Query("Path", new WebMeta("Path", String.IsNullOrEmpty(parentPath) ? "/" : parentPath)) : UIClick.UIEvent("Search", new WebMeta("key", "Path").Put("search", new WebMeta("Path", path.Substring(0, name))));// UIClick.Click(new UIClick(request, "Path", path.Substring(0, name), "Model", "Next")); if (sftpFiles.Count() == 0) { var cell = UICell.Create("UI", new WebMeta("value", path.Substring(name + 1)).Put("text", "当前目录").Put("Icon", '\uf0e8').Put("click", click)); pathUI.Delete(cell, new UIEventText("删除").Click(UIClick.Click(new UIClick(request, g, "Del", "Path", path + "/")))); var desc = new UIDesc($"当前目录没有文件或子目录"); desc.Put("icon", "\uf016").Format("desc", "{icon}\n{desc}"); desc.Style.Align(1).Color(0xaaa).Padding(20, 20).BgColor(0xfff).Size(12).Name("icon", new UIStyle().Font("wdk").Size(60)); pathUI.Add(desc); } else { pathUI.AddCell('\uf0e8', "当前目录", path.Substring(name + 1), click); } } foreach (var file in sftpFiles) { if (file.IsDirectory) { pathUI.AddCell('\uf114', file.Name, "", request.IsApp ? UIClick.Query("Path", new WebMeta("Path", file.FullName)) : UIClick.UIEvent("Search", new WebMeta("key", "Path").Put("search", new WebMeta("Path", file.FullName))));// ("Path", file.FullName))); } else { var cell = UICell.Create("UI", new WebMeta("value", Utility.GetBitSize(file.Length)).Put("text", file.Name).Put("Icon", '\uf0f6').Put("click", new UIClick(request, g, file.Length > 1000000 ? "Down" : "View", "Path", file.FullName))); pathUI.Delete(cell, new UIEventText("删除").Click(new UIClick(request, g, "Del", "Path", file.FullName))); } } } } catch (Exception ex) { var desc = new UIDesc(ex.Message); pathUI.Add(desc); } ui.UIFootBar = new UIFootBar() { IsFixed = true }; ui.UIFootBar.AddIcon(new UIEventText('\uf0ee', "上传").Click(new UIClick(request, g, "UploadFile"))); ui.UIFootBar.AddText(new UIEventText("新建目录").Click(new UIClick(request, g, "New")), new UIEventText("终端打开").Click(new UIClick(request, g, "Open")).Style(new UIStyle().BgColor())); ui.SendTo($"OpenPath,UploadFile,{request.Model}.{request.Command}.Remove", this.Context, true, $"{request.Model}.{request.Command}"); }); var Path = this.AsyncDialog("Path", "/"); var password = UMC.Data.DataFactory.Instance().Password(SiteConfig.MD5Key(device.Ip, device.Username)); // switch (model) { case "Next": { var webMeta = new WebMeta(request.Arguments); webMeta.Remove("Model"); this.Context.Send("ViewPath", new WebMeta().Put("Path", Path), false); this.Context.Send(new WebMeta().UIEvent("Search", this.AsyncDialog("UI", "none"), new WebMeta("key", "Path").Put("send", webMeta)), true); } break; case "Editer": { var config = this.AsyncDialog("Settings", g => { var from = new Web.UIFormDialog() { Title = "设备" }; from.AddTextValue().Put("登录地址", $"{device.Ip}:{device.Port}").Put("用户名", device.Username); from.AddText("名称", "Caption", device.Caption); from.AddText("描述", "Desc", device.Desc).NotRequired();//.Put("tip", String.Empty); from.Submit("确认修改", $"{this.Context.Request.Model}.{this.Context.Request.Command}"); return from; }); Data.HotCache.Put(new Entities.Device { Id = device.Id, Desc = config["Desc"], Caption = config["Caption"] }); this.Context.Send($"{this.Context.Request.Model}.{this.Context.Request.Command}", true); } break; case "UploadFile": if (request.IsApp) { request.Arguments["Model"] = "File"; goto case "Dir"; } else { this.Context.Send("UploadFile", new WebMeta().Put("Path", Path), true); } break; case "Dir": case "File": { var media_id = this.AsyncDialog("media_id", g => { if (request.IsApp) { return Web.UIDialog.Create("File"); } else { var from = new Web.UIFormDialog() { Title = "上传文件" }; var conf = from.AddFile("选择资源", "media_id", String.Empty).Put("Accept", "*/*").Put("Multiple", true); switch (model) { case "Dir": from.Title = "上传文件夹"; conf.Put("Dir", true); break; } from.Submit("确认上传"); return from; } }); var deviceKey = Utility.Guid(this.AsyncDialog("Device", "none")); var filePath = String.Format("{0}{1}", Path, Path.EndsWith('/') ? "" : "/"); if (media_id.StartsWith("http://") || media_id.StartsWith("https://")) { var url = new Uri(media_id); var name = Uri.UnescapeDataString(url.AbsolutePath.Substring(url.AbsolutePath.LastIndexOf('/') + 1)); url.WebRequest().Get(r => { if (r.StatusCode == System.Net.HttpStatusCode.OK) { var tempFile = System.IO.Path.GetTempFileName(); var stream = System.IO.File.Open(tempFile, System.IO.FileMode.Create); r.ReadAsData((b, i, c) => { if (c == 0 && b.Length == 0) { if (i == -1) { this.Prompt("Web SSH", "上传失败", false); stream.Close(); System.IO.File.Delete(tempFile); } else { stream.Flush(); stream.Close(); try { Upload(name, filePath, tempFile, device, password, deviceKey); this.Prompt("上传成功", false); } catch (Exception ex) { this.Prompt("上传错误", ex.Message, false); } } this.Context.Send(false); this.Context.OutputFinish(); } else { stream.Write(b, i, c); } }); } else { this.Prompt("Web SSH", "上传失败", false); this.Context.OutputFinish(); } }); response.Redirect(Empty); } else { media_id = Uri.UnescapeDataString(media_id); var filename = UMC.Data.Reflection.ConfigPath(String.Format("Static\\TEMP\\{0}", media_id.Substring(media_id.IndexOf('/', 2) + 1))); var name = media_id.Substring(media_id.LastIndexOf('/') + 1); try { Upload(name, filePath, filename, device, password, deviceKey); } catch (Exception ex) { this.Prompt("上传错误", ex.Message); } this.Context.Send(true); } } break; case "Open": if (request.IsApp) { String url; if (Path == "/") { url = new Uri(request.Url, $"/WebSSH/{device.Id}/").AbsoluteUri; } else { url = new Uri(request.Url, $"/WebSSH/{device.Id}{Path}/").AbsoluteUri; } var webMeta = new WebMeta(); webMeta.Put("value", url); webMeta.Put("right", new UIEventText().Icon('\uf11c').Click(UIClick.Click(new UIClick(request, "Model", "Keyboard")))); this.Context.Send("OpenUrl", webMeta, true); } else { this.Context.Send("OpenPath", new WebMeta().Put("Path", Path), true); } break; case "Keyboard": { var ui = this.AsyncDialog("UI", "none"); var Paste = this.AsyncDialog("PasteText", r => { var s = new UISheetDialog() { Title = "快捷键" }; s.CloseEvent = $"{request.Model}.{request.Command}"; s.Cells(3); s.Put(new UIClick("Event", new WebMeta().UIEvent("UI.Callback", ui, new WebMeta().Put("type", "Keyboard", "value", "\u001b"))) { Text = "ESC" }); s.Put(new UIClick("Event", new WebMeta().UIEvent("UI.Callback", ui, new WebMeta().Put("type", "Keyboard", "value", "\u001b[A"))) { Text = "↑" }); s.Put(new UIClick("Event", new WebMeta().UIEvent("UI.Callback", ui, new WebMeta().Put("type", "Keyboard", "value", "\u0003"))) { Text = "Ctrl + C" }); s.Put(new UIClick("Event", new WebMeta().UIEvent("UI.Callback", ui, new WebMeta().Put("type", "Keyboard", "value", "\u001b[D"))) { Text = "←" }); s.Put(new UIClick("CaseCMS", new UIClick(request)) { Text = "粘贴" }); // s.Put(new UIClick("Event", new WebMeta().UIEvent("UI.Refresh", ui, new WebMeta().Put("type", "Keyboard", "value", "\t"))) { Text = "刷新" }); s.Put(new UIClick("Event", new WebMeta().UIEvent("UI.Callback", ui, new WebMeta().Put("type", "Keyboard", "value", "\u001b[C"))) { Text = "→" }); s.Put(new UIClick("Event", new WebMeta().UIEvent("UI.Callback", ui, new WebMeta().Put("type", "Keyboard", "value", "\u001b[H"))) { Text = "Home" }); s.Put(new UIClick("Event", new WebMeta().UIEvent("UI.Callback", ui, new WebMeta().Put("type", "Keyboard", "value", "\u001b[B"))) { Text = "↓" }); s.Put(new UIClick("Event", new WebMeta().UIEvent("UI.Callback", ui, new WebMeta().Put("type", "Keyboard", "value", "\u001b[F"))) { Text = "END" }); return s; }); this.Context.Send(new WebMeta().UIEvent("UI.Callback", ui, new WebMeta().Put("type", "Keyboard", "value", Paste)), true); } break; case "New": { var filePath = String.Format("{0}{1}", Path, Path.EndsWith('/') ? "" : "/"); var dir = this.AsyncDialog("DirName", r => new UITextDialog() { Title = "新建文件夹" }); using (var sftp = new SftpClient(new PasswordConnectionInfo(device.Ip, device.Port ?? 22, device.Username, password))) { sftp.Connect(); sftp.CreateDirectory(filePath + dir); } this.Context.Send(true); } break; case "Download": response.Redirect(new Uri(request.Url, $"/WebSSH/{device.Id}{Path}")); break; case "Edit": this.Context.Send("Markdown", new WebMeta().Put("submit", new UIClick(request, "Model", "Content")), true); break; case "Down": case "View": this.AsyncDialog("View", r => { var value = UIClick.Markdown(new UIClick(request, "Model", "Content")); value.Text = "编辑"; var s = new UISheetDialog() { Title = "文件选择" }; s.Put(new UIClick(new Uri(request.Url, $"/WebSSH/{device.Id}{Path}").AbsoluteUri) { Key = "Url", Text = "下载" }); if (model == "View") s.Put(value); return s; }); break; case "Content": { using (var sftp = new SftpClient(new PasswordConnectionInfo(device.Ip, device.Port ?? 22, device.Username, password))) { sftp.Connect(); var Content = this.AsyncDialog("Content", r => { String s = String.Empty; try { s = sftp.ReadAllText(Path); } catch (Exception ex) { this.Prompt("打开失败", ex.Message); } response.Redirect(new WebMeta().Put("Content", s).Put("title", Path)); }); sftp.WriteAllText(Path, Content); SSHLog(device, this.Context.Token.Username, $"upload {Path}"); } } break; case "Remove": { UMC.Data.HotCache.Delete(new Entities.Device { Id = Utility.IntParse(Key, 0) }); this.Context.Send($"{request.Model}.{request.Command}.Remove", true); } break; case "Del": { using (var sftp = new SftpClient(new PasswordConnectionInfo(device.Ip, device.Port ?? 22, device.Username, password))) { sftp.Connect(); if (Path.EndsWith('/')) { var delPath = Path.Substring(0, Path.Length - 1); sftp.DeleteDirectory(delPath); SSHLog(device, this.Context.Token.Username, $"rmdir {delPath}"); var webMeta = new WebMeta(request.Arguments); webMeta.Remove("Model"); var nameIndex = delPath.LastIndexOf('/'); webMeta.Put("Path", nameIndex == 0 ? "/" : delPath.Substring(0, nameIndex)); this.Context.Send("ViewPath", new WebMeta().Put("Path", webMeta["Path"]), false); this.Context.Send(new WebMeta().UIEvent("Search", this.AsyncDialog("UI", "none"), new WebMeta("key", "Path").Put("send", webMeta)), true); } else { sftp.Delete(Path); SSHLog(device, this.Context.Token.Username, $"rm {Path}"); } } } break; } } void Upload(string name, string path, string filename, Entities.Device device, string password, Guid? deviceKey) { var filePath = String.Format("{0}{1}", path, path.EndsWith('/') ? "" : "/", name); using (var sftp = new SftpClient(new PasswordConnectionInfo(device.Ip, device.Port ?? 22, device.Username, password))) { sftp.Connect(); if (name.EndsWith(".umczip")) { var fs = ZipFile.OpenRead(filename).Entries; var fsCount = fs.Count; int ucount = 0; foreach (var f in fs) { ucount++; if (f.Length > 0) { var fnames = new List(f.FullName.Split('/')); fnames.RemoveAt(fnames.Count - 1); var sbf = new System.Text.StringBuilder(); sbf.Append(filePath); for (var i = 0; i < fnames.Count; i++) { if (i > 0) { sbf.Append('/'); } sbf.Append(fnames[i]); if (sftp.Exists(sbf.ToString()) == false) { sftp.CreateDirectory(sbf.ToString()); } } using (var stm = f.Open()) { var now = Utility.TimeSpan(); sftp.UploadFile(stm, filePath + f.FullName, true, (v) => { if (deviceKey.HasValue) { var old = Utility.TimeSpan(); if (old - now > 0) { Host.HttpWebSocket.Send(deviceKey.Value, UMC.Data.JSON.Serialize(new WebMeta("type", "progress", "value", $"{f.Name}({ucount}/{fsCount}) {v * 100 / ((ulong)f.Length)}%"))); now = old; } } }); SSHLog(device, this.Context.Token.Username, $"upload {filePath + f.FullName}"); } } } } else { using (var s = System.IO.File.OpenRead(filename)) { var now = Utility.TimeSpan(); sftp.UploadFile(s, filePath + name, true, (v) => { if (deviceKey.HasValue) { var old = Utility.TimeSpan(); if (old - now > 0) { Host.HttpWebSocket.Send(deviceKey.Value, UMC.Data.JSON.Serialize(new WebMeta("type", "progress", "value", $"{name} {v * 100 / ((ulong)s.Length)}%"))); now = old; } } }); SSHLog(device, this.Context.Token.Username, $"upload {filePath + name}"); } } System.IO.File.Delete(filename); } } void Create() { if (this.Context.Request.IsMaster == false) { this.Prompt("只有管理员才能新增设备"); } var config = this.AsyncDialog("Settings", g => { var from = new Web.UIFormDialog() { Title = "新增设备" }; from.AddText("名称", "Caption", String.Empty); from.AddText("地址", "Ip", String.Empty).PlaceHolder("IP地址"); from.AddText("端口", "Port", "22"); from.AddText("用户名", "Username", String.Empty); from.AddPassword("密码", "Password", String.Empty); from.AddText("描述", "Desc", String.Empty).NotRequired().Put("tip", String.Empty); from.Submit("确认新增", $"{this.Context.Request.Model}.{this.Context.Request.Command}"); return from; }); var device = new Entities.Device { Id = Utility.TimeSpan(), Ip = config["Ip"], Port = Utility.IntParse(config["Port"], 22), Username = config["Username"], Desc = config["Desc"], Caption = config["Caption"], Type = Entities.DeviceType.Linux }; var ssh = new SshClient(device.Ip, device.Port.Value, device.Username, config["Password"]); try { ssh.Connect(); ssh.Disconnect(); UMC.Data.HotCache.Put(device); UMC.Data.DataFactory.Instance().Password(new Guid(Utility.MD5(device.Ip, device.Username)), config["Password"]); this.Prompt("新增成功", false); } catch (Exception ex) { this.Prompt("异常", ex.Message); } this.Context.Send($"{this.Context.Request.Model}.{this.Context.Request.Command}", true); } }