|
@@ -0,0 +1,349 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
+using System.Text;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using System.Windows;
|
|
|
+using System.Windows.Controls;
|
|
|
+using System.Windows.Data;
|
|
|
+using System.Windows.Documents;
|
|
|
+using System.Windows.Input;
|
|
|
+using System.Windows.Media;
|
|
|
+using System.Windows.Media.Imaging;
|
|
|
+using System.Windows.Navigation;
|
|
|
+using System.Windows.Shapes;
|
|
|
+using System.Net.Http;
|
|
|
+using System.Net.Http.Json;
|
|
|
+using Websocket.Client;
|
|
|
+using Ookii.Dialogs.Wpf;
|
|
|
+using System.Text.Json;
|
|
|
+
|
|
|
+namespace pmtest_client
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// Interaction logic for MainWindow.xaml
|
|
|
+ /// </summary>
|
|
|
+ public partial class MainWindow : Window
|
|
|
+ {
|
|
|
+ private System.Timers.Timer autoRefreshTimer;
|
|
|
+ private HttpClient httpClient;
|
|
|
+ private WebsocketClient? wsClient;
|
|
|
+ public MainWindow()
|
|
|
+ {
|
|
|
+ InitializeComponent();
|
|
|
+ autoRefreshTimer = new System.Timers.Timer();
|
|
|
+ autoRefreshTimer.Interval = 1000;
|
|
|
+ autoRefreshTimer.Elapsed += AutoRefreshTimer_Elapsed;
|
|
|
+ autoRefreshTimer.Enabled = false;
|
|
|
+ httpClient = new HttpClient();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void PrintLog(Brush color, String text)
|
|
|
+ {
|
|
|
+ Dispatcher.Invoke(() =>
|
|
|
+ {
|
|
|
+ Run t = new Run("[" + DateTime.Now.ToString() + "] ");
|
|
|
+ t.Foreground = Brushes.Navy;
|
|
|
+ Run tb = new Run(text);
|
|
|
+ tb.Foreground = color;
|
|
|
+ logPara.Inlines.Add(t);
|
|
|
+ logPara.Inlines.Add(tb);
|
|
|
+ logPara.Inlines.Add(new LineBreak());
|
|
|
+ rtbLogBox.ScrollToEnd();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AutoRefreshTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
|
|
|
+ {
|
|
|
+ DoStatusRefresh();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnSetAutoRefresh_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ autoRefreshTimer?.Start();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnManualRefresh_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ DoStatusRefresh();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnStopAutoRefresh_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ autoRefreshTimer?.Stop();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void DoStatusRefresh()
|
|
|
+ {
|
|
|
+ Dispatcher.InvokeAsync(async () =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ httpClient.DefaultRequestHeaders.Remove("AuthKey");
|
|
|
+ httpClient.DefaultRequestHeaders.Add("AuthKey", tbAccessKey.Text);
|
|
|
+ HttpResponseMessage response = await httpClient.GetAsync(String.Format("http://{0}/", tbURLRoot.Text));
|
|
|
+ string responseBody = await response.Content.ReadAsStringAsync();
|
|
|
+ PrintLog(Brushes.LightBlue, "Status refreshed.");
|
|
|
+ Dispatcher.Invoke(() =>
|
|
|
+ {
|
|
|
+ tbStatusDisplay.Text = responseBody;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Crimson, String.Format("Failed do status refresh: {0}", e.Message));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnConnectCmdOut_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ if (wsClient == null)
|
|
|
+ {
|
|
|
+ Dispatcher.InvokeAsync(async () =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var uri = new Uri(String.Format("ws://{0}/ws/get-cmd-output.satori?auth-key={1}", tbURLRoot.Text, tbAccessKey.Text));
|
|
|
+ wsClient = new WebsocketClient(uri);
|
|
|
+ wsClient.IsReconnectionEnabled = false;
|
|
|
+ wsClient.MessageReceived.Subscribe(msg =>
|
|
|
+ {
|
|
|
+ Dispatcher.Invoke(() =>
|
|
|
+ {
|
|
|
+ tbCmdOut.AppendText(msg.Text);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ wsClient.DisconnectionHappened.Subscribe(msg =>
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.RoyalBlue, "CmdOutput websocket Disconnected.");
|
|
|
+ wsClient.Dispose();
|
|
|
+ wsClient = null;
|
|
|
+ });
|
|
|
+ PrintLog(Brushes.RoyalBlue, "CmdOuput websocket connecting...");
|
|
|
+ await wsClient.Start();
|
|
|
+ PrintLog(Brushes.RoyalBlue, "CmdOuput websocket connected.");
|
|
|
+ }
|
|
|
+ catch (Exception err)
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Crimson, string.Format("Websocket error: {0}", err.Message));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnDisconnectCmdOut_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ if (wsClient != null)
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.RoyalBlue, "Disconnect CmdOutput websocket connection...");
|
|
|
+ Dispatcher.InvokeAsync(async () =>
|
|
|
+ {
|
|
|
+ await wsClient.Stop(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "stop by user.");
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private bool checkApiResponse(Dictionary<string, string> result)
|
|
|
+ {
|
|
|
+ if (result.ContainsKey("status"))
|
|
|
+ {
|
|
|
+ if (result["status"] == "200")
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (result.ContainsKey("errMsg"))
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Orange, string.Format("API Error: {0}", result["errMsg"]));
|
|
|
+ PrintLog(Brushes.PaleVioletRed, serializeResponseJson(result));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Orange, "API Error: Unknown Error");
|
|
|
+ PrintLog(Brushes.PaleVioletRed, serializeResponseJson(result));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Orange, "Invalid response: 'status' field not found.");
|
|
|
+ PrintLog(Brushes.PaleVioletRed, serializeResponseJson(result));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private string serializeResponseJson(Dictionary<string, string> result)
|
|
|
+ {
|
|
|
+ var options = new JsonSerializerOptions { WriteIndented = true };
|
|
|
+ string jsonString = JsonSerializer.Serialize(result, options);
|
|
|
+ return jsonString;
|
|
|
+ }
|
|
|
+ private void btnCreateTaskDo_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ JsonDef_Req_CreateTask req = new JsonDef_Req_CreateTask();
|
|
|
+ req.TaskName = tbCreateTaskName.Text;
|
|
|
+ req.ExecutablePath = tbCreateTaskExec.Text;
|
|
|
+ req.WorkDir = tbCreateTaskWDir.Text;
|
|
|
+ req.Args = CommandLineSplit.SplitArgs(tbCreateTaskArgs.Text);
|
|
|
+ int etmo = 0;
|
|
|
+ if (!int.TryParse(tbCreateTaskETmO.Text, out etmo))
|
|
|
+ {
|
|
|
+ etmo = 0;
|
|
|
+ }
|
|
|
+ req.ExecTimeout = etmo;
|
|
|
+ Dispatcher.InvokeAsync(async () =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ httpClient.DefaultRequestHeaders.Remove("AuthKey");
|
|
|
+ httpClient.DefaultRequestHeaders.Add("AuthKey", tbAccessKey.Text);
|
|
|
+ HttpResponseMessage response = await httpClient.PostAsJsonAsync<JsonDef_Req_CreateTask>(
|
|
|
+ String.Format("http://{0}/api/new-task.satori", tbURLRoot.Text),
|
|
|
+ req
|
|
|
+ );
|
|
|
+ Dictionary<string, string> result = await response.Content.ReadFromJsonAsync<Dictionary<string, string>>();
|
|
|
+ if (result != null)
|
|
|
+ {
|
|
|
+ if (checkApiResponse(result))
|
|
|
+ {
|
|
|
+ if (result.ContainsKey("cpid") && result.ContainsKey("name"))
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.SeaGreen, string.Format("Task created: CPID={0}, Name={1}", result["cpid"], result["name"]));
|
|
|
+ btnCreateTaskGenNameUUID_Click(null, null);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Orange, "Invalid response: missing some fields.");
|
|
|
+ PrintLog(Brushes.PaleVioletRed, serializeResponseJson(result));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Orange, "Invalid response: null");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Crimson, String.Format("Failed create task: {0}", e.Message));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnCreateTaskGenBrowseWorkDir_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ VistaFolderBrowserDialog fbd = new VistaFolderBrowserDialog();
|
|
|
+ if (fbd.ShowDialog() == true)
|
|
|
+ {
|
|
|
+ tbCreateTaskWDir.Text = fbd.SelectedPath;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnCreateTaskGenBrowseExec_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ VistaOpenFileDialog ofd = new VistaOpenFileDialog();
|
|
|
+ ofd.DefaultExt = ".exe";
|
|
|
+ ofd.Filter = "exe files|*.exe|All files|*";
|
|
|
+ if (ofd.ShowDialog() == true)
|
|
|
+ {
|
|
|
+ tbCreateTaskExec.Text = ofd.FileName;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnCreateTaskGenNameUUID_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ Guid guid = Guid.NewGuid();
|
|
|
+ tbCreateTaskName.Text = String.Format("task-{0}", guid.ToString());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnCreateDaemonBrowseExec_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ VistaOpenFileDialog ofd = new VistaOpenFileDialog();
|
|
|
+ ofd.DefaultExt = ".exe";
|
|
|
+ ofd.Filter = "exe files|*.exe|All files|*";
|
|
|
+ if (ofd.ShowDialog() == true)
|
|
|
+ {
|
|
|
+ tbCreateDaemonExec.Text = ofd.FileName;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnCreateDaemonBrowseWorkDir_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ VistaFolderBrowserDialog fbd = new VistaFolderBrowserDialog();
|
|
|
+ if (fbd.ShowDialog() == true)
|
|
|
+ {
|
|
|
+ tbCreateDaemonWDir.Text = fbd.SelectedPath;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnCreateDaemonDo_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+ JsonDef_Req_CreateDaemon req = new JsonDef_Req_CreateDaemon();
|
|
|
+ req.DaemonName = tbCreateDaemonName.Text;
|
|
|
+ req.ExecutablePath = tbCreateDaemonExec.Text;
|
|
|
+ req.WorkDir = tbCreateDaemonWDir.Text;
|
|
|
+ req.Args = CommandLineSplit.SplitArgs(tbCreateDaemonArgs.Text);
|
|
|
+ req.EnableAfterCreate = cbCreateDaemonStartAfterC.IsChecked?.Value;
|
|
|
+ Dispatcher.InvokeAsync(async () =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ httpClient.DefaultRequestHeaders.Remove("AuthKey");
|
|
|
+ httpClient.DefaultRequestHeaders.Add("AuthKey", tbAccessKey.Text);
|
|
|
+ HttpResponseMessage response = await httpClient.PostAsJsonAsync<JsonDef_Req_CreateTask>(
|
|
|
+ String.Format("http://{0}/api/new-task.satori", tbURLRoot.Text),
|
|
|
+ req
|
|
|
+ );
|
|
|
+ Dictionary<string, string> result = await response.Content.ReadFromJsonAsync<Dictionary<string, string>>();
|
|
|
+ if (result != null)
|
|
|
+ {
|
|
|
+ if (checkApiResponse(result))
|
|
|
+ {
|
|
|
+ if (result.ContainsKey("cpid") && result.ContainsKey("name"))
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.SeaGreen, string.Format("Task created: CPID={0}, Name={1}", result["cpid"], result["name"]));
|
|
|
+ btnCreateTaskGenNameUUID_Click(null, null);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Orange, "Invalid response: missing some fields.");
|
|
|
+ PrintLog(Brushes.PaleVioletRed, serializeResponseJson(result));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Orange, "Invalid response: null");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ PrintLog(Brushes.Crimson, String.Format("Failed create task: {0}", e.Message));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnSetDaemonEnable_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void btnSetDaemonDisable_Click(object sender, RoutedEventArgs e)
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|