You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Gateway/Proxy/SiteSSHActivities.cs

679 lines
32 KiB
C#

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<String>();
var user = this.Context.Token.Identity();
var searcher = UMC.Data.HotCache.Search<Entities.Device>();
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<Entities.Device>();
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<String>(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);
}
}