Build server connection testing working.

This commit is contained in:
Scott Duensing 2023-01-22 20:38:46 -06:00
parent b7ec93dc3c
commit 7d116c6706
9 changed files with 457 additions and 119 deletions

View file

@ -37,20 +37,21 @@ option(DEBUG_MODE "Enable debugging output and memory tracing?" ON)
set(CMAKE_C_STANDARD 99)
set(SOURCE_FILES
thirdparty-installed/memwatch/memwatch.c
ui/generated/resources.c
src/main.c
src/utils.c
src/joeydev.c
src/vector.c
src/array.c
src/draw.c
src/image.c
src/vecparse.c
src/color.c
src/palette.c
src/project.c
src/ssh.c
thirdparty-installed/memwatch/memwatch.c
ui/generated/resources.c
src/main.c
src/utils.c
src/joeydev.c
src/vector.c
src/array.c
src/draw.c
src/image.c
src/vecparse.c
src/color.c
src/palette.c
src/project.c
src/ssh.c
src/http.c
)
configure_file(include/config.h.in config.h)
@ -110,9 +111,9 @@ target_link_libraries(${CMAKE_PROJECT_NAME}
${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libssh2.a
${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libgcrypt.a
${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libgpg-error.a
${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libcurl.a
${CMAKE_SOURCE_DIR}/thirdparty-installed/lib/libz.a
${GTK3_LIBRARIES}
# -lssl
-ldl
-pthread
-lm

173
src/http.c Normal file
View file

@ -0,0 +1,173 @@
/*
* JoeyDev
* Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include "http.h"
static CURLM *_curlMulti = NULL;
static HTTPT **_activeHTTPs = NULL;
gboolean httpUpdate(gpointer userData); // Not static
static size_t httpWrite(char *data, size_t num, size_t length, void *userp);
HTTPT *httpDownload(HTTPCallback callback, char *filename, char *url) {
HTTPT *download = NULL;
CURL *eh = NULL;
download = NEW(HTTPT);
if (download) {
download->file = fopen(filename, "wb");
if (download->file) {
download->filename = strdup(filename);
download->length = 0;
download->progress = 0;
download->finished = FALSE;
download->success = FALSE;
download->callback = callback;
eh = curl_easy_init();
#ifdef DEBUG_MODE
//curl_easy_setopt(eh, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(eh, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(eh, CURLOPT_SSL_VERIFYHOST, 0L);
#endif
curl_easy_setopt(eh, CURLOPT_BUFFERSIZE, CURL_MAX_READ_SIZE);
curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, httpWrite);
curl_easy_setopt(eh, CURLOPT_WRITEDATA, download);
curl_easy_setopt(eh, CURLOPT_URL, url);
curl_multi_add_handle(_curlMulti, eh);
arrput(_activeHTTPs, download);
} else {
DEL(download);
}
}
return download;
}
void httpShutdown(void) {
//***TODO*** Any active sessions?
g_idle_remove_by_data(httpUpdate);
curl_multi_cleanup(_curlMulti);
curl_global_cleanup();
}
void httpStartup(void) {
curl_global_init(CURL_GLOBAL_ALL);
_curlMulti = curl_multi_init();
g_idle_add(httpUpdate, httpUpdate);
}
gboolean httpUpdate(gpointer userData) {
CURLcode code;
CURL *eh = NULL;
CURLMsg *msg = NULL;
HTTPT *dl = NULL;
int msgsLeft = -1;
int stillAlive = 1;
long httpCode = 0;
int i;
(void)userData;
for (i=0; i<arrlen(_activeHTTPs); i++) {
dl = _activeHTTPs[i];
curl_multi_perform(_curlMulti, &stillAlive);
while ((msg = curl_multi_info_read(_curlMulti, &msgsLeft))) {
code = msg->data.result;
eh = msg->easy_handle;
// Try to get remote file size.
if (dl->length == 0) {
curl_easy_getinfo(eh, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &dl->length);
#ifdef DEBUG_MODE
debug("CURL Length %" CURL_FORMAT_CURL_OFF_T "\n", dl->length);
#endif
}
if (msg->msg == CURLMSG_DONE) {
// HTTP Error?
curl_easy_getinfo (eh, CURLINFO_RESPONSE_CODE, &httpCode);
// Finished. Tell CURL.
curl_multi_remove_handle(_curlMulti, eh);
curl_easy_cleanup(eh);
// Close local file.
fclose(dl->file);
// Transfer okay?
dl->success = (code == CURLE_OK && httpCode == 200);
dl->finished = TRUE;
// Nuke anything we may have written if transfer was bad.
if (dl->success == FALSE) {
unlink(dl->filename);
}
// Callback whoever issued this download.
if (dl->callback) {
dl->callback(dl);
}
// Remove from download list.
arrdel(_activeHTTPs, i);
// Free resources.
DEL(dl->filename);
DEL(dl);
break;
#ifdef DEBUG_MODE
} else {
debug("CURL: %d\n", code);
#endif
}
}
if (stillAlive) {
curl_multi_wait(_curlMulti, NULL, 0, 100, NULL);
}
}
return G_SOURCE_CONTINUE;
}
static size_t httpWrite(char *data, size_t num, size_t length, void *userp) {
HTTPT *download = (HTTPT *)userp;
// Write it to disk.
fwrite(data, num, length, download->file);
// Update UI.
download->progress += num * length;
#ifdef DEBUG_MODE
debug("CURL Progress %" CURL_FORMAT_CURL_OFF_T " Length %" CURL_FORMAT_CURL_OFF_T "\n", download->progress, download->length);
#endif
// Did the user request to cancel the transfer?
/*
if (download->finished) {
return CURL_READFUNC_ABORT;
}
*/
return num * length;
}

56
src/http.h Normal file
View file

@ -0,0 +1,56 @@
/*
* JoeyDev
* Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef HTTP_H
#define HTTP_H
#include "common.h"
#include "curl/curl.h"
struct HTTPS;
typedef void (*HTTPCallback)(struct HTTPS*);
typedef struct HTTPS {
CURLM *cm;
FILE *file;
char *filename;
gboolean finished;
gboolean success;
curl_off_t length;
curl_off_t progress;
HTTPCallback callback;
GtkWidget *status;
} HTTPT;
HTTPT *httpDownload(HTTPCallback callback, char *filename, char *url);
void httpShutdown(void);
void httpStartup(void);
#endif // HTTP_H

View file

@ -24,12 +24,14 @@
#include "common.h"
#include "scintillaHeaders.h"
#include "joeydev.h"
#include "http.h"
#include "ssh.h"
int main(int argc, char **argv) {
gtk_init(&argc, &argv);
httpStartup();
sshStartup();
winJoeyDevCreate();
@ -37,6 +39,7 @@ int main(int argc, char **argv) {
scintilla_release_resources();
sshShutdown();
httpShutdown();
return 0;
}

View file

@ -22,11 +22,13 @@
#pragma clang diagnostic push
#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"
#pragma ide diagnostic ignored "EndlessLoop"
#include "common.h"
#include "project.h"
#include "utils.h"
#include "http.h"
#include "ssh.h"
@ -56,7 +58,8 @@ typedef struct ProjectDataS {
GtkWidget *treeProject;
char *configName;
char *buildHost;
int buildPort;
int buildHTTPPort;
int buildSSHPort;
char *buildUser;
char *buildPassword;
} ProjectDataT;
@ -81,26 +84,31 @@ static SectionDataT _sectionData[] = {
};
static gboolean _httpTestComplete = FALSE;
static gboolean _httpTestSuccess = FALSE;
#define BUILD_SETTINGS_RESPONSE_TEST 1
static void addToTree(ProjectDataT *self, char *filename);
static void loadConfig(ProjectDataT *self);
static void loadProject(ProjectDataT *self);
EVENT void menuProjectFileNew(GtkWidget *object, gpointer userData);
EVENT void menuProjectFileOpen(GtkWidget *object, gpointer userData);
EVENT void menuProjectFileSave(GtkWidget *object, gpointer userData);
EVENT void menuProjectFileSaveAs(GtkWidget *object, gpointer userData);
EVENT void menuProjectFileClose(GtkWidget *object, gpointer userData);
EVENT void menuProjectProjectAdd(GtkWidget *object, gpointer userData);
EVENT void menuProjectProjectRemove(GtkWidget *object, gpointer userData);
EVENT void menuProjectProjectProperties(GtkWidget *object, gpointer userData);
EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData);
EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData);
EVENT void menuProjectHelpProject(GtkWidget *object, gpointer userData);
static void saveConfig(ProjectDataT *self);
EVENT gboolean winProjectClose(GtkWidget *object, gpointer userData);
static void winProjectDelete(gpointer userData);
static void addToTree(ProjectDataT *self, char *filename);
static void httpTest(HTTPT *download);
static void loadConfig(ProjectDataT *self);
static void loadProject(ProjectDataT *self);
EVENT void menuProjectFileNew(GtkWidget *object, gpointer userData);
EVENT void menuProjectFileOpen(GtkWidget *object, gpointer userData);
EVENT void menuProjectFileSave(GtkWidget *object, gpointer userData);
EVENT void menuProjectFileSaveAs(GtkWidget *object, gpointer userData);
EVENT void menuProjectFileClose(GtkWidget *object, gpointer userData);
EVENT void menuProjectProjectAdd(GtkWidget *object, gpointer userData);
EVENT void menuProjectProjectRemove(GtkWidget *object, gpointer userData);
EVENT void menuProjectProjectProperties(GtkWidget *object, gpointer userData);
EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData);
EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData);
EVENT void menuProjectHelpProject(GtkWidget *object, gpointer userData);
static void saveConfig(ProjectDataT *self);
EVENT gboolean winProjectClose(GtkWidget *object, gpointer userData);
static void winProjectDelete(gpointer userData);
static void addToTree(ProjectDataT *self, char *filename) {
@ -134,6 +142,12 @@ static void addToTree(ProjectDataT *self, char *filename) {
}
static void httpTest(HTTPT *download) {
_httpTestComplete = TRUE;
_httpTestSuccess = download->success;
}
static void loadConfig(ProjectDataT *self) {
FILE *in = NULL;
char *line = NULL;
@ -156,16 +170,20 @@ static void loadConfig(ProjectDataT *self) {
self->buildHost = strdup(line);
break;
case 3: // Port
self->buildPort = atoi(line);
case 3: // HTTP Port
self->buildHTTPPort = atoi(line);
break;
case 4: // User
case 4: // SSH Port
self->buildSSHPort = atoi(line);
break;
case 5: // User
DEL(self->buildUser);
self->buildUser = strdup(line);
break;
case 5: // Password
case 6: // Password
DEL(self->buildPassword);
self->buildPassword = strdup(line);
break;
@ -187,7 +205,8 @@ static void loadConfig(ProjectDataT *self) {
DEL(self->buildPassword);
self->buildHost = strdup("build.joeylib.com");
self->buildPort = 816;
self->buildHTTPPort = 6502;
self->buildSSHPort = 816;
self->buildUser = strdup(g_get_user_name());
self->buildPassword = strdup("SuperSecret");
@ -396,39 +415,53 @@ EVENT void menuProjectProjectProperties(GtkWidget *object, gpointer userData) {
EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData) {
ProjectDataT *self = (ProjectDataT *)userData;
GtkWidget *dialog;
GtkWidget *dialogServerSettings;
GtkWidget *txtHost;
GtkWidget *txtPort;
GtkWidget *txtHTTPPort;
GtkWidget *txtSSHPort;
GtkWidget *txtUser;
GtkWidget *txtPassword;
GtkWidget *btnTest;
GtkWidget *btnOkay;
char *widgetNames[] = {
"dialogServerSettings",
"txtHost",
"txtPort",
"txtHTTPPort",
"txtSSHPort",
"txtUser",
"txtPassword",
"btnTest",
"btnOkay",
NULL
};
GtkWidget **widgets[] = {
&dialogServerSettings,
&txtHost,
&txtPort,
&txtHTTPPort,
&txtSSHPort,
&txtUser,
&txtPassword
&txtPassword,
&btnTest,
&btnOkay
};
char temp[6];
char *name = NULL;
char *url = NULL;
int result = 0;
SSHT *ssh = NULL;
char *output = NULL;
char *error = NULL;
(void)object;
utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/BuildServer.glade", widgetNames, widgets, self);
while (1) {
snprintf(temp, 6, "%d", self->buildPort);
gtk_entry_set_text(GTK_ENTRY(txtHost), self->buildHost);
gtk_entry_set_text(GTK_ENTRY(txtPort), temp);
snprintf(temp, 6, "%d", self->buildHTTPPort);
gtk_entry_set_text(GTK_ENTRY(txtHTTPPort), temp);
snprintf(temp, 6, "%d", self->buildSSHPort);
gtk_entry_set_text(GTK_ENTRY(txtSSHPort), temp);
gtk_entry_set_text(GTK_ENTRY(txtUser), self->buildUser);
gtk_entry_set_text(GTK_ENTRY(txtPassword), self->buildPassword);
result = gtk_dialog_run(GTK_DIALOG(dialogServerSettings));
@ -438,28 +471,56 @@ EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData) {
DEL(self->buildUser);
DEL(self->buildPassword);
self->buildHost = strdup(gtk_entry_get_text(GTK_ENTRY(txtHost)));
self->buildPort = atoi(gtk_entry_get_text(GTK_ENTRY(txtPort)));
self->buildHTTPPort = atoi(gtk_entry_get_text(GTK_ENTRY(txtHTTPPort)));
self->buildSSHPort = atoi(gtk_entry_get_text(GTK_ENTRY(txtSSHPort)));
self->buildUser = strdup(gtk_entry_get_text(GTK_ENTRY(txtUser)));
self->buildPassword = strdup(gtk_entry_get_text(GTK_ENTRY(txtPassword)));
saveConfig(self);
if (result == BUILD_SETTINGS_RESPONSE_TEST) {
// Run server connection test.
ssh = sshConnect(self->buildHost, self->buildPort, self->buildUser, self->buildPassword);
if (ssh) {
debug("Connected!\n");
// echo ${JOEYLIB_SDKS}
result = sshExecute(ssh, "ls -la", &output);
debug("----------\n");
if ((result == 0) && (strlen(output) > 0)) {
debug("%s", output);
} else {
debug("No output!\n");
}
debug("----------\n");
DEL(output);
sshDisconnect(&ssh);
gtk_widget_set_sensitive(btnTest, FALSE);
gtk_widget_set_sensitive(btnOkay, FALSE);
// Run server connection tests - HTTP.
name = utilCreateString("%s%cjoeydev%cjoeydev.info", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR);
url = utilCreateString("http://%s:%d/joeydev.info", self->buildHost, self->buildHTTPPort);
_httpTestComplete = FALSE;
httpDownload(httpTest, name, url);
// This loop seriously breaks CLion's syntax highlighting.
while (_httpTestComplete == FALSE) {
gtk_main_iteration();
}
if (_httpTestSuccess == FALSE) {
error = utilCreateString("Unable to connect to HTTP port.");
}
DEL(url);
DEL(name);
// Run server connection tests - SSH.
ssh = sshConnect(self->buildHost, self->buildSSHPort, self->buildUser, self->buildPassword);
if (ssh) {
sshDisconnect(&ssh);
} else {
DEL(error);
error = utilCreateString("Unable to connect to SSH port.");
}
// Did the tests pass?
if (error == NULL) {
dialog = gtk_message_dialog_new(GTK_WINDOW(dialogServerSettings),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_MESSAGE_INFO,
GTK_BUTTONS_CLOSE,
"Successful connection to build server!");
} else {
dialog = gtk_message_dialog_new(GTK_WINDOW(dialogServerSettings),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"%s", error);
}
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
DEL(error);
gtk_widget_set_sensitive(btnTest, TRUE);
gtk_widget_set_sensitive(btnOkay, TRUE);
} else {
// Close dialog on OKAY but not TEST.
break;
@ -500,7 +561,8 @@ static void saveConfig(ProjectDataT *self) {
fprintf(out, "%s\n", BUILD_SETTINGS_VERSION);
fprintf(out, "------------------------------------------------------------------------------\n");
fprintf(out, "%s\n", self->buildHost);
fprintf(out, "%d\n", self->buildPort);
fprintf(out, "%d\n", self->buildHTTPPort);
fprintf(out, "%d\n", self->buildSSHPort);
fprintf(out, "%s\n", self->buildUser);
fprintf(out, "%s\n", self->buildPassword); //***TODO*** This is hardly secure.
fclose(out);

View file

@ -105,7 +105,7 @@ SSHT *sshConnect(char *hostname, uint16_t port, char *user, char *password) {
;
if (rc) {
debug("Failure authenticating SSH session: %d\n", rc);
while(libssh2_session_disconnect(data->session, "Error") == LIBSSH2_ERROR_EAGAIN)
while (libssh2_session_disconnect(data->session, "Error") == LIBSSH2_ERROR_EAGAIN)
;
libssh2_session_free(data->session);
socketclose(data->sock);
@ -130,10 +130,10 @@ void sshDisconnect(SSHT **sshData) {
int sshExecute(SSHT *sshData, char *command, char **output) {
int rc;
int exitcode = 127;
char *exitsignal = (char *)"none";
int outputLen = 0;
int rc;
int exitcode = 127;
char *exitsignal = (char *)"none";
int outputLen = 0;
utilEnsureBufferSize((unsigned char **)output, &outputLen, 1024);
*output[0] = 0;
@ -144,7 +144,7 @@ int sshExecute(SSHT *sshData, char *command, char **output) {
if (sshData->channel == NULL) {
return exitcode;
}
while((rc = libssh2_channel_exec(sshData->channel, command)) == LIBSSH2_ERROR_EAGAIN) {
while ((rc = libssh2_channel_exec(sshData->channel, command)) == LIBSSH2_ERROR_EAGAIN) {
sshWaitSocket(sshData->sock, sshData->session);
}
if (rc != 0) {
@ -210,8 +210,7 @@ SSHT *sshExecuteVerbose(SSHT *sshData, char *title, char *command, SSHCallback c
void sshShutdown(void) {
//curl_multi_cleanup(_curlMulti);
//curl_global_cleanup();
//***TODO*** Any active sessions?
g_idle_remove_by_data(sshUpdate);
@ -225,9 +224,6 @@ void sshShutdown(void) {
void sshStartup(void) {
int err;
//curl_global_init(CURL_GLOBAL_ALL);
//_curlMulti = curl_multi_init();
#ifdef WIN32
err = WSAStartup(MAKEWORD(2, 0), &_wsadata);
if (err != 0) {

BIN
thirdparty/curl-7.87.0.tar.bz2 (Stored with Git LFS) vendored Normal file

Binary file not shown.

View file

@ -108,6 +108,24 @@ pushd "${ROOT}" || exit &> /dev/null
popd || true &> /dev/null
fi
if [[ ! -f ${INSTALLED}/lib/libcurl.a ]]; then
echo Building Dependency: libcurl...
tar xjf ${THIRDPARTY}/curl-7.87.0.tar.bz2
pushd curl-7.87.0 || exit &> /dev/null
./configure \
--enable-static \
--disable-shared \
--without-ssl \
--without-libidn2 \
--without-libpsl \
--without-brotli \
--without-zstd \
--prefix=${INSTALLED}
make
make install
popd || true &> /dev/null
fi
if [[ ! -f ${INSTALLED}/lib/scintilla.a ]]; then
echo Building Dependency: Scintilla...
tar xzf ${THIRDPARTY}/scintilla531.tgz

View file

@ -51,7 +51,7 @@
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=4 -->
<!-- n-columns=2 n-rows=5 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
@ -70,45 +70,6 @@
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Port:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">User:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Password:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="txtHost">
<property name="visible">True</property>
@ -122,15 +83,55 @@
</packing>
</child>
<child>
<object class="GtkEntry" id="txtPort">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Password:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">4</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="txtPassword">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="input-purpose">digits</property>
<property name="visibility">False</property>
<property name="input-purpose">password</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
<property name="top-attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">User:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">SSH Port:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
@ -140,21 +141,46 @@
<property name="hexpand">True</property>
<property name="input-purpose">name</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="txtSSHPort">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="input-purpose">digits</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="txtPassword">
<object class="GtkEntry" id="txtHTTPPort">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="input-purpose">password</property>
<property name="input-purpose">digits</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">3</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">HTTP Port:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>