network tab
This commit is contained in:
@@ -127,7 +127,6 @@ impl GurtHandler for AppHandler {
|
||||
};
|
||||
|
||||
log::info!("Handler started for {} {} from {}", ctx.method(), ctx.path(), ctx.remote_addr);
|
||||
log::info!("Handler type will be: {:?}", handler_type);
|
||||
|
||||
let result = match handler_type {
|
||||
HandlerType::Index => routes::index(app_state).await,
|
||||
@@ -337,7 +336,6 @@ async fn get_ca_certificate_content(app_state: &AppState) -> std::result::Result
|
||||
|
||||
async fn serve_static_file(ctx: &ServerContext) -> Result<GurtResponse> {
|
||||
let path = ctx.path();
|
||||
log::info!("Static file request for path: '{}'", path);
|
||||
|
||||
// Strip query parameters from the path for static file serving
|
||||
let path_without_query = if let Some(query_pos) = path.find('?') {
|
||||
@@ -355,7 +353,6 @@ async fn serve_static_file(ctx: &ServerContext) -> Result<GurtResponse> {
|
||||
path_without_query
|
||||
}
|
||||
};
|
||||
log::info!("Resolved file_path: '{}'", file_path);
|
||||
|
||||
if file_path.contains("..") || file_path.contains('/') || file_path.contains('\\') {
|
||||
log::warn!("Invalid file path requested: '{}'", file_path);
|
||||
@@ -367,11 +364,9 @@ async fn serve_static_file(ctx: &ServerContext) -> Result<GurtResponse> {
|
||||
.map_err(|_| GurtError::invalid_message("Failed to get current directory"))?;
|
||||
let frontend_dir = current_dir.join("frontend");
|
||||
let full_path = frontend_dir.join(file_path);
|
||||
log::info!("Attempting to read file: '{}'", full_path.display());
|
||||
|
||||
match tokio::fs::read_to_string(&full_path).await {
|
||||
Ok(content) => {
|
||||
log::info!("Successfully read file, content length: {} bytes", content.len());
|
||||
let content_type = match full_path.extension().and_then(|ext| ext.to_str()) {
|
||||
Some("html") => "text/html",
|
||||
Some("lua") => "text/plain",
|
||||
|
||||
@@ -21,30 +21,22 @@ fn parse_query_string(query: &str) -> HashMap<String, String> {
|
||||
}
|
||||
|
||||
pub(crate) async fn index(_app_state: AppState) -> Result<GurtResponse> {
|
||||
log::info!("Index handler called - attempting to serve index.html");
|
||||
|
||||
let current_dir = std::env::current_dir()
|
||||
.map_err(|_| GurtError::invalid_message("Failed to get current directory"))?;
|
||||
log::info!("Current directory: {}", current_dir.display());
|
||||
|
||||
let frontend_dir = current_dir.join("frontend");
|
||||
let index_path = frontend_dir.join("index.html");
|
||||
log::info!("Looking for index.html at: {}", index_path.display());
|
||||
|
||||
match tokio::fs::read_to_string(&index_path).await {
|
||||
Ok(content) => {
|
||||
log::info!("Successfully read index.html, content length: {} bytes", content.len());
|
||||
Ok(GurtResponse::ok()
|
||||
.with_header("Content-Type", "text/html")
|
||||
.with_string_body(&content))
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to read index.html: {}", e);
|
||||
Err(_) => {
|
||||
let body = format!(
|
||||
"GurtDNS v{}!\n\nThe available endpoints are:\n\n - [GET] /domains\n - [GET] /domain/{{name}}/{{tld}}\n - [POST] /domain\n - [PUT] /domain/{{key}}\n - [DELETE] /domain/{{key}}\n - [GET] /tlds\n\nRatelimits are as follows: 5 requests per 10 minutes on `[POST] /domain`.\n\nCode link: https://github.com/outpoot/gurted",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
);
|
||||
log::info!("Serving fallback API info, length: {} bytes", body.len());
|
||||
Ok(GurtResponse::ok().with_string_body(&body))
|
||||
}
|
||||
}
|
||||
@@ -353,8 +345,6 @@ pub(crate) async fn get_user_domains(
|
||||
app_state: AppState,
|
||||
claims: Claims,
|
||||
) -> Result<GurtResponse> {
|
||||
log::info!("get_user_domains called for user_id: {} path: {}", claims.user_id, ctx.path());
|
||||
|
||||
// Parse pagination from query parameters
|
||||
let path = ctx.path();
|
||||
let query_params = if let Some(query_start) = path.find('?') {
|
||||
|
||||
1
flumi/Assets/Icons/arrow-down-up.svg
Normal file
1
flumi/Assets/Icons/arrow-down-up.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-down-up-icon lucide-arrow-down-up"><path d="m3 16 4 4 4-4"/><path d="M7 20V4"/><path d="m21 8-4-4-4 4"/><path d="M17 4v16"/></svg>
|
||||
|
After Width: | Height: | Size: 333 B |
37
flumi/Assets/Icons/arrow-down-up.svg.import
Normal file
37
flumi/Assets/Icons/arrow-down-up.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dv4nw2ly6hcrr"
|
||||
path="res://.godot/imported/arrow-down-up.svg-e4447ec644884e537fc08b18ae7bb6dc.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/arrow-down-up.svg"
|
||||
dest_files=["res://.godot/imported/arrow-down-up.svg-e4447ec644884e537fc08b18ae7bb6dc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
flumi/Assets/Icons/braces.svg
Normal file
1
flumi/Assets/Icons/braces.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-braces-icon lucide-braces"><path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1"/><path d="M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1"/></svg>
|
||||
|
After Width: | Height: | Size: 394 B |
37
flumi/Assets/Icons/braces.svg.import
Normal file
37
flumi/Assets/Icons/braces.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://k6yp5f5io1mh"
|
||||
path="res://.godot/imported/braces.svg-be0ddfb3b648ce88f7cbe89a363017a8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/braces.svg"
|
||||
dest_files=["res://.godot/imported/braces.svg-be0ddfb3b648ce88f7cbe89a363017a8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
flumi/Assets/Icons/case-sensitive.svg
Normal file
1
flumi/Assets/Icons/case-sensitive.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-case-sensitive-icon lucide-case-sensitive"><path d="m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16"/><path d="M22 9v7"/><path d="M3.304 13h6.392"/><circle cx="18.5" cy="12.5" r="3.5"/></svg>
|
||||
|
After Width: | Height: | Size: 381 B |
37
flumi/Assets/Icons/case-sensitive.svg.import
Normal file
37
flumi/Assets/Icons/case-sensitive.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cxv5qglx3e52a"
|
||||
path="res://.godot/imported/case-sensitive.svg-86490276d97b2dfa5c8d69e899cd6fc8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/case-sensitive.svg"
|
||||
dest_files=["res://.godot/imported/case-sensitive.svg-86490276d97b2dfa5c8d69e899cd6fc8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
flumi/Assets/Icons/file-text.svg
Normal file
1
flumi/Assets/Icons/file-text.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-text-icon lucide-file-text"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>
|
||||
|
After Width: | Height: | Size: 400 B |
37
flumi/Assets/Icons/file-text.svg.import
Normal file
37
flumi/Assets/Icons/file-text.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bo1jlyb03xum4"
|
||||
path="res://.godot/imported/file-text.svg-353070fe8162736240f258d043a74bb8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/file-text.svg"
|
||||
dest_files=["res://.godot/imported/file-text.svg-353070fe8162736240f258d043a74bb8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
flumi/Assets/Icons/image.svg
Normal file
1
flumi/Assets/Icons/image.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-image-icon lucide-image"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>
|
||||
|
After Width: | Height: | Size: 366 B |
37
flumi/Assets/Icons/image.svg.import
Normal file
37
flumi/Assets/Icons/image.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bpkr68d2gousl"
|
||||
path="res://.godot/imported/image.svg-cb997eeaa724d7751463205eebbff36d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/image.svg"
|
||||
dest_files=["res://.godot/imported/image.svg-cb997eeaa724d7751463205eebbff36d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
flumi/Assets/Icons/palette.svg
Normal file
1
flumi/Assets/Icons/palette.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-palette-icon lucide-palette"><path d="M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z"/><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/></svg>
|
||||
|
After Width: | Height: | Size: 575 B |
37
flumi/Assets/Icons/palette.svg.import
Normal file
37
flumi/Assets/Icons/palette.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://brm4a7hb5316w"
|
||||
path="res://.godot/imported/palette.svg-5fa12e1482da68f886eda54557eaaf30.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/palette.svg"
|
||||
dest_files=["res://.godot/imported/palette.svg-5fa12e1482da68f886eda54557eaaf30.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
12
flumi/Resources/NetworkRequestItem_Normal.tres
Normal file
12
flumi/Resources/NetworkRequestItem_Normal.tres
Normal file
@@ -0,0 +1,12 @@
|
||||
[gd_resource type="StyleBoxFlat" format=3 uid="uid://bxmcq4y0i4hl4"]
|
||||
|
||||
[resource]
|
||||
content_margin_left = 5.0
|
||||
content_margin_top = 5.0
|
||||
content_margin_right = 5.0
|
||||
content_margin_bottom = 5.0
|
||||
bg_color = Color(0.188371, 0.188371, 0.188371, 1)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
12
flumi/Resources/NetworkRequestItem_Selected.tres
Normal file
12
flumi/Resources/NetworkRequestItem_Selected.tres
Normal file
@@ -0,0 +1,12 @@
|
||||
[gd_resource type="StyleBoxFlat" format=3 uid="uid://clvnmfql36jit"]
|
||||
|
||||
[resource]
|
||||
content_margin_left = 5.0
|
||||
content_margin_top = 5.0
|
||||
content_margin_right = 5.0
|
||||
content_margin_bottom = 5.0
|
||||
bg_color = Color(0.2, 0.4, 0.8, 0.3)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
@@ -1,17 +1,15 @@
|
||||
[gd_scene load_steps=19 format=3 uid="uid://cgav3xl2xgupb"]
|
||||
[gd_scene load_steps=26 format=3 uid="uid://cgav3xl2xgupb"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://vrobqac6makc" path="res://Scripts/DevToolsConsole.gd" id="2_3m6n9"]
|
||||
[ext_resource type="Texture2D" uid="uid://custohlvwclqs" path="res://Assets/Icons/eraser.svg" id="3_6hj4c"]
|
||||
[ext_resource type="Texture2D" uid="uid://cqg4eny0nyojd" path="res://Assets/Icons/funnel.svg" id="4_ynqb1"]
|
||||
[ext_resource type="SyntaxHighlighter" uid="uid://d0aeuvwp0545i" path="res://Resources/LuaSyntaxHighlighter.tres" id="5_xkykt"]
|
||||
[ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="6_8muo7"]
|
||||
[ext_resource type="Script" uid="uid://dh3jdrot4r7m3" path="res://Scripts/NetworkTab.gd" id="7_network"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6hj4c"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6hj4c"]
|
||||
content_margin_left = 2.0
|
||||
content_margin_top = 2.0
|
||||
content_margin_right = 2.0
|
||||
content_margin_bottom = 2.0
|
||||
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qb7hm"]
|
||||
@@ -120,8 +118,62 @@ corner_radius_bottom_left = 8
|
||||
expand_margin_left = 4.0
|
||||
expand_margin_right = 4.0
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y2h58"]
|
||||
content_margin_left = 8.0
|
||||
content_margin_top = 5.0
|
||||
content_margin_right = 5.0
|
||||
content_margin_bottom = 5.0
|
||||
bg_color = Color(0.125911, 0.125911, 0.125911, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(0.498039, 0.498039, 0.498039, 1)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_couro"]
|
||||
content_margin_left = 8.0
|
||||
content_margin_top = 5.0
|
||||
content_margin_right = 5.0
|
||||
content_margin_bottom = 5.0
|
||||
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.498039, 0.498039, 0.498039, 1)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7sefc"]
|
||||
content_margin_left = 8.0
|
||||
content_margin_top = 5.0
|
||||
content_margin_right = 5.0
|
||||
content_margin_bottom = 5.0
|
||||
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(0.498039, 0.498039, 0.498039, 1)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xkykt"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c62ub"]
|
||||
content_margin_top = 5.0
|
||||
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
|
||||
|
||||
[node name="DevTools" type="VBoxContainer"]
|
||||
custom_minimum_size = Vector2(450, 400)
|
||||
custom_minimum_size = Vector2(495, 400)
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="TabContainer" type="TabContainer" parent="."]
|
||||
@@ -136,13 +188,21 @@ theme_override_styles/tab_selected = SubResource("StyleBoxFlat_ynqb1")
|
||||
theme_override_styles/tab_hovered = SubResource("StyleBoxFlat_8muo7")
|
||||
theme_override_styles/tab_unselected = SubResource("StyleBoxFlat_xkykt")
|
||||
tab_alignment = 1
|
||||
current_tab = 0
|
||||
current_tab = 1
|
||||
drag_to_rearrange_enabled = true
|
||||
|
||||
[node name="Elements" type="Label" parent="TabContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "Elements tab - Coming soon"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
metadata/_tab_index = 0
|
||||
|
||||
[node name="Console" type="VBoxContainer" parent="TabContainer"]
|
||||
layout_mode = 2
|
||||
script = ExtResource("2_3m6n9")
|
||||
metadata/_tab_index = 0
|
||||
metadata/_tab_index = 1
|
||||
|
||||
[node name="Toolbar" type="HBoxContainer" parent="TabContainer/Console"]
|
||||
layout_mode = 2
|
||||
@@ -196,7 +256,6 @@ layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_up43w")
|
||||
theme_override_styles/focus = SubResource("StyleBoxFlat_qb3ke")
|
||||
text = "test"
|
||||
placeholder_text = "Enter Lua code..."
|
||||
scroll_fit_content_height = true
|
||||
caret_blink = true
|
||||
@@ -210,14 +269,6 @@ auto_brace_completion_highlight_matching = true
|
||||
|
||||
[node name="PositioningTimer" type="Timer" parent="TabContainer/Console/InputContainer"]
|
||||
|
||||
[node name="Elements" type="Label" parent="TabContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "Elements tab - Coming soon"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
metadata/_tab_index = 1
|
||||
|
||||
[node name="Sources" type="Label" parent="TabContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
@@ -226,13 +277,187 @@ horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
metadata/_tab_index = 2
|
||||
|
||||
[node name="Network" type="Label" parent="TabContainer"]
|
||||
[node name="Network" type="VBoxContainer" parent="TabContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "Network tab - Coming soon"
|
||||
script = ExtResource("7_network")
|
||||
metadata/_tab_index = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Network"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="StatusBar" type="HBoxContainer" parent="TabContainer/Network/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="RequestCount" type="Label" parent="TabContainer/Network/HBoxContainer/StatusBar"]
|
||||
layout_mode = 2
|
||||
text = "0 requests"
|
||||
|
||||
[node name="Transfer" type="Label" parent="TabContainer/Network/HBoxContainer/StatusBar"]
|
||||
layout_mode = 2
|
||||
text = "0 B transferred"
|
||||
|
||||
[node name="Loaded" type="Label" parent="TabContainer/Network/HBoxContainer/StatusBar"]
|
||||
layout_mode = 2
|
||||
text = "0 resources loaded"
|
||||
|
||||
[node name="Control" type="Control" parent="TabContainer/Network/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="FilterDropdown" type="OptionButton" parent="TabContainer/Network/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme = ExtResource("6_8muo7")
|
||||
theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1)
|
||||
theme_override_colors/font_hover_color = Color(1, 1, 1, 1)
|
||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||
theme_override_colors/font_focus_color = Color(1, 1, 1, 1)
|
||||
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_y2h58")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_couro")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_7sefc")
|
||||
selected = 0
|
||||
item_count = 9
|
||||
popup/item_0/text = "All"
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = "Fetch"
|
||||
popup/item_1/id = 1
|
||||
popup/item_2/text = "Doc"
|
||||
popup/item_2/id = 2
|
||||
popup/item_3/text = "CSS"
|
||||
popup/item_3/id = 3
|
||||
popup/item_4/text = "Lua"
|
||||
popup/item_4/id = 4
|
||||
popup/item_5/text = "Font"
|
||||
popup/item_5/id = 5
|
||||
popup/item_6/text = "Img"
|
||||
popup/item_6/id = 6
|
||||
popup/item_7/text = "Socket"
|
||||
popup/item_7/id = 7
|
||||
popup/item_8/text = "Other"
|
||||
popup/item_8/id = 8
|
||||
|
||||
[node name="HSeparator2" type="HSeparator" parent="TabContainer/Network"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 14
|
||||
|
||||
[node name="MainContainer" type="HSplitContainer" parent="TabContainer/Network"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="LeftPanel" type="VBoxContainer" parent="TabContainer/Network/MainContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HeaderRow" type="HBoxContainer" parent="TabContainer/Network/MainContainer/LeftPanel"]
|
||||
custom_minimum_size = Vector2(0, 28)
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="IconHeader" type="Control" parent="TabContainer/Network/MainContainer/LeftPanel/HeaderRow"]
|
||||
custom_minimum_size = Vector2(25, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NameHeader" type="Label" parent="TabContainer/Network/MainContainer/LeftPanel/HeaderRow"]
|
||||
custom_minimum_size = Vector2(120, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Name"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="StatusHeader" type="Label" parent="TabContainer/Network/MainContainer/LeftPanel/HeaderRow"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(60, 0)
|
||||
layout_mode = 2
|
||||
text = "Status"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
metadata/_tab_index = 3
|
||||
|
||||
[node name="TypeHeader" type="Label" parent="TabContainer/Network/MainContainer/LeftPanel/HeaderRow"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(60, 0)
|
||||
layout_mode = 2
|
||||
text = "Type"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="SizeHeader" type="Label" parent="TabContainer/Network/MainContainer/LeftPanel/HeaderRow"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
text = "Size"
|
||||
horizontal_alignment = 2
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="TimeHeader" type="Label" parent="TabContainer/Network/MainContainer/LeftPanel/HeaderRow"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(80, 0)
|
||||
layout_mode = 2
|
||||
text = "Time"
|
||||
horizontal_alignment = 2
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/Network/MainContainer/LeftPanel"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="RequestList" type="VBoxContainer" parent="TabContainer/Network/MainContainer/LeftPanel/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="RightPanel" type="VBoxContainer" parent="TabContainer/Network/MainContainer"]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(300, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="TabContainer/Network/MainContainer/RightPanel"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_6hj4c")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Network/MainContainer/RightPanel/PanelContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="CloseButton" type="Button" parent="TabContainer/Network/MainContainer/RightPanel/PanelContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(32, 32)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 0
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_xkykt")
|
||||
text = "✕"
|
||||
flat = true
|
||||
|
||||
[node name="TabContainer" type="TabContainer" parent="TabContainer/Network/MainContainer/RightPanel/PanelContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/side_margin = 0
|
||||
theme_override_styles/tab_focus = SubResource("StyleBoxEmpty_6hj4c")
|
||||
theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_6hj4c")
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_c62ub")
|
||||
theme_override_styles/tab_selected = SubResource("StyleBoxFlat_ynqb1")
|
||||
theme_override_styles/tab_hovered = SubResource("StyleBoxFlat_8muo7")
|
||||
theme_override_styles/tab_unselected = SubResource("StyleBoxFlat_xkykt")
|
||||
current_tab = 0
|
||||
|
||||
[node name="Headers" type="VBoxContainer" parent="TabContainer/Network/MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer"]
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 0
|
||||
|
||||
[node name="Preview" type="VBoxContainer" parent="TabContainer/Network/MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 1
|
||||
|
||||
[node name="Response" type="VBoxContainer" parent="TabContainer/Network/MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 2
|
||||
|
||||
[node name="Application" type="Label" parent="TabContainer"]
|
||||
visible = false
|
||||
@@ -241,3 +466,6 @@ text = "Application tab - Coming soon"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
metadata/_tab_index = 4
|
||||
|
||||
[connection signal="item_selected" from="TabContainer/Network/HBoxContainer/FilterDropdown" to="TabContainer/Network" method="_on_filter_selected"]
|
||||
[connection signal="pressed" from="TabContainer/Network/MainContainer/RightPanel/PanelContainer/HBoxContainer/CloseButton" to="TabContainer/Network" method="hide_details_panel"]
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dqvgywj71hfry"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://dqvgywj71hfry"]
|
||||
|
||||
[ext_resource type="Script" path="res://Scripts/NetworkRequestItem.gd" id="1_8v2qr"]
|
||||
[ext_resource type="Script" uid="uid://bcs7m624uvv3x" path="res://Scripts/NetworkRequestItem.gd" id="1_8v2qr"]
|
||||
[ext_resource type="StyleBox" uid="uid://bxmcq4y0i4hl4" path="res://Resources/NetworkRequestItem_Normal.tres" id="2_normal"]
|
||||
[ext_resource type="StyleBox" uid="uid://clvnmfql36jit" path="res://Resources/NetworkRequestItem_Selected.tres" id="3_selected"]
|
||||
|
||||
[node name="NetworkRequestItem" type="PanelContainer"]
|
||||
custom_minimum_size = Vector2(0, 28)
|
||||
theme_override_styles/panel = ExtResource("2_normal")
|
||||
script = ExtResource("1_8v2qr")
|
||||
metadata/normal_style = ExtResource("2_normal")
|
||||
metadata/selected_style = ExtResource("3_selected")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="IconContainer" type="Control" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(20, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Icon" type="TextureRect" parent="HBoxContainer/IconContainer"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
@@ -26,41 +33,43 @@ offset_right = 8.0
|
||||
offset_bottom = 8.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
|
||||
[node name="NameLabel" type="Label" parent="HBoxContainer"]
|
||||
clip_contents = true
|
||||
custom_minimum_size = Vector2(140, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
custom_minimum_size = Vector2(140, 0)
|
||||
text = "Request Name"
|
||||
clip_contents = true
|
||||
text_overrun_behavior = 3
|
||||
vertical_alignment = 1
|
||||
text_overrun_behavior = 3
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(60, 0)
|
||||
layout_mode = 2
|
||||
text = "200"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
metadata/success_color = Color(0, 1, 0, 1)
|
||||
metadata/error_color = Color(1, 0, 0, 1)
|
||||
metadata/pending_color = Color(1, 1, 0, 1)
|
||||
|
||||
[node name="TypeLabel" type="Label" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(60, 0)
|
||||
layout_mode = 2
|
||||
text = "Fetch"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="SizeLabel" type="Label" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(60, 0)
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(80, 0)
|
||||
text = "1.2 KB"
|
||||
horizontal_alignment = 2
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="TimeLabel" type="Label" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(80, 0)
|
||||
layout_mode = 2
|
||||
text = "125ms"
|
||||
horizontal_alignment = 2
|
||||
vertical_alignment = 1
|
||||
@@ -39,5 +39,9 @@ var HTML_CONTENT = """
|
||||
<head>
|
||||
<title>New tab</title>
|
||||
</head>
|
||||
<body><p>test</p></body>
|
||||
<body>
|
||||
<p>Welcome to Flumi Browser!</p>
|
||||
<img src="https://httpbin.org/image/png" alt="Test image" />
|
||||
<p>This page includes a test image to verify network functionality.</p>
|
||||
</body>
|
||||
""".to_utf8_buffer()
|
||||
|
||||
@@ -26,7 +26,6 @@ func initialize_filter() -> void:
|
||||
current_filter = ""
|
||||
|
||||
func connect_signals() -> void:
|
||||
Trace.get_instance().log_message.connect(_on_trace_message)
|
||||
clear_button.pressed.connect(_on_clear_pressed)
|
||||
|
||||
input_line.gui_input.connect(_on_input_gui_input)
|
||||
@@ -37,9 +36,6 @@ func load_existing_logs() -> void:
|
||||
for msg in existing_messages:
|
||||
add_log_entry(msg.message, msg.level, msg.timestamp)
|
||||
|
||||
func _on_trace_message(message: Variant, level: String, timestamp: float) -> void:
|
||||
call_deferred("add_log_entry", message, level, timestamp)
|
||||
|
||||
func _on_lua_print(message: String) -> void:
|
||||
add_log_entry(message, "lua", Time.get_ticks_msec() / 1000.0)
|
||||
|
||||
@@ -84,31 +80,14 @@ func add_log_entry(message: Variant, level: String, timestamp: float) -> void:
|
||||
|
||||
func create_log_item(entry: Dictionary) -> Control:
|
||||
if entry.level == "input":
|
||||
var message_code_edit = CodeEdit.new()
|
||||
var input_display_text = get_display_text_for_entry(entry)
|
||||
message_code_edit.text = input_display_text
|
||||
message_code_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
message_code_edit.scroll_fit_content_height = true
|
||||
message_code_edit.editable = true
|
||||
message_code_edit.context_menu_enabled = true
|
||||
message_code_edit.shortcut_keys_enabled = true
|
||||
message_code_edit.selecting_enabled = true
|
||||
message_code_edit.deselect_on_focus_loss_enabled = true
|
||||
message_code_edit.drag_and_drop_selection_enabled = false
|
||||
message_code_edit.virtual_keyboard_enabled = false
|
||||
message_code_edit.middle_mouse_paste_enabled = false
|
||||
|
||||
var code_style_normal = StyleBoxFlat.new()
|
||||
code_style_normal.bg_color = Color.TRANSPARENT
|
||||
code_style_normal.border_width_left = 0
|
||||
code_style_normal.border_width_top = 0
|
||||
code_style_normal.border_width_right = 0
|
||||
code_style_normal.border_width_bottom = 0
|
||||
code_style_normal.content_margin_bottom = 8
|
||||
message_code_edit.add_theme_stylebox_override("normal", code_style_normal)
|
||||
message_code_edit.add_theme_stylebox_override("focus", code_style_normal)
|
||||
|
||||
message_code_edit.syntax_highlighter = input_line.syntax_highlighter.duplicate()
|
||||
var message_code_edit = CodeEditUtils.create_code_edit({
|
||||
"text": input_display_text,
|
||||
"scroll_fit_content_height": true,
|
||||
"transparent_background": true,
|
||||
"syntax_highlighter": input_line.syntax_highlighter.duplicate(),
|
||||
"block_editing_signals": true
|
||||
})
|
||||
|
||||
message_code_edit.gui_input.connect(_on_log_code_edit_gui_input)
|
||||
message_code_edit.focus_entered.connect(_on_log_code_edit_focus_entered.bind(message_code_edit))
|
||||
|
||||
@@ -2,7 +2,7 @@ extends RefCounted
|
||||
class_name GurtProtocol
|
||||
|
||||
const DNS_SERVER_IP: String = "135.125.163.131"
|
||||
const DNS_SERVER_PORT: int = 8877
|
||||
const DNS_SERVER_PORT: int = 4878
|
||||
|
||||
static func is_gurt_domain(url: String) -> bool:
|
||||
if url.begins_with("gurt://"):
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
uid://cer1rniskhi24
|
||||
@@ -3,7 +3,7 @@ class_name LuaSyntaxHighlighter
|
||||
extends SyntaxHighlighter
|
||||
|
||||
@export_group("Colors")
|
||||
@export var font_color: Color = Color("#d4d4d4", Color.WHITE)
|
||||
@export var font_color: Color = Color.from_string("#d4d4d4", Color.WHITE)
|
||||
@export var keyword_color: Color = Color.from_string("#c586c0", Color.WHITE)
|
||||
@export var gurt_globals_color: Color = Color.from_string("#569cd6", Color.WHITE)
|
||||
@export var function_color: Color = Color.from_string("#dcdcaa", Color.WHITE)
|
||||
@@ -48,9 +48,8 @@ func _init() -> void:
|
||||
for m in members:
|
||||
_member_keywords[m] = true
|
||||
|
||||
func _is_whitespace(char: String) -> bool:
|
||||
return char == " " or char == "\t"
|
||||
|
||||
func _is_whitespace(ch: String) -> bool:
|
||||
return ch == " " or ch == "\t"
|
||||
|
||||
func _clear_highlighting_cache():
|
||||
_state_cache.clear()
|
||||
@@ -142,9 +141,9 @@ func _get_line_syntax_highlighting(p_line: int) -> Dictionary:
|
||||
if current_char == "0" and i + 1 < line_len and line_text[i+1].to_lower() == "x":
|
||||
i += 2; is_hex = true
|
||||
while i < line_len:
|
||||
var char = line_text[i]
|
||||
if (is_hex and char.is_valid_hex_number(false)) or \
|
||||
(not is_hex and (char.is_valid_int() or char in "Ee.-+")):
|
||||
var ch = line_text[i]
|
||||
if (is_hex and ch.is_valid_hex_number(false)) or \
|
||||
(not is_hex and (ch.is_valid_int() or ch in "Ee.-+")):
|
||||
i += 1
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -7,12 +7,19 @@ func fetch_image(url: String) -> ImageTexture:
|
||||
if url.is_empty():
|
||||
return null
|
||||
|
||||
var network_request = NetworkManager.start_request(url, "GET", false)
|
||||
|
||||
var request_headers = PackedStringArray()
|
||||
request_headers.append("User-Agent: " + UserAgent.get_user_agent())
|
||||
|
||||
var headers_dict = {}
|
||||
headers_dict["User-Agent"] = UserAgent.get_user_agent()
|
||||
NetworkManager.set_request_headers(network_request.id, headers_dict)
|
||||
|
||||
var error = http_request.request(url, request_headers)
|
||||
if error != OK:
|
||||
print("Error making HTTP request: ", error)
|
||||
NetworkManager.fail_request(network_request.id, "HTTP request error: " + str(error))
|
||||
http_request.queue_free()
|
||||
return null
|
||||
|
||||
@@ -25,10 +32,19 @@ func fetch_image(url: String) -> ImageTexture:
|
||||
|
||||
http_request.queue_free()
|
||||
|
||||
var response_headers = {}
|
||||
for header in headers:
|
||||
var parts = header.split(":", 1)
|
||||
if parts.size() == 2:
|
||||
response_headers[parts[0].strip_edges()] = parts[1].strip_edges()
|
||||
|
||||
if result != HTTPRequest.RESULT_SUCCESS or response_code != 200:
|
||||
print("Failed to fetch image. Result: ", result, " Response code: ", response_code)
|
||||
NetworkManager.complete_request(network_request.id, response_code, "Request failed", response_headers, body.get_string_from_utf8(), body)
|
||||
return null
|
||||
|
||||
NetworkManager.complete_request(network_request.id, response_code, "OK", response_headers, body.get_string_from_utf8(), body)
|
||||
|
||||
# Get content type from headers
|
||||
var content_type = ""
|
||||
for header in headers:
|
||||
@@ -74,12 +90,19 @@ func fetch_text(url: String) -> String:
|
||||
http_request.queue_free()
|
||||
return ""
|
||||
|
||||
var network_request = NetworkManager.start_request(url, "GET", false)
|
||||
|
||||
var request_headers = PackedStringArray()
|
||||
request_headers.append("User-Agent: " + UserAgent.get_user_agent())
|
||||
|
||||
var headers_dict = {}
|
||||
headers_dict["User-Agent"] = UserAgent.get_user_agent()
|
||||
NetworkManager.set_request_headers(network_request.id, headers_dict)
|
||||
|
||||
var error = http_request.request(url, request_headers)
|
||||
if error != OK:
|
||||
print("Error making HTTP request for text resource: ", url, " Error: ", error)
|
||||
NetworkManager.fail_request(network_request.id, "HTTP request error: " + str(error))
|
||||
http_request.queue_free()
|
||||
return ""
|
||||
|
||||
@@ -87,15 +110,27 @@ func fetch_text(url: String) -> String:
|
||||
|
||||
var result = response[0] # HTTPClient.Result
|
||||
var response_code = response[1] # int
|
||||
var headers = response[2] # PackedStringArray
|
||||
var body = response[3] # PackedByteArray
|
||||
|
||||
http_request.queue_free()
|
||||
|
||||
var response_headers = {}
|
||||
for header in headers:
|
||||
var parts = header.split(":", 1)
|
||||
if parts.size() == 2:
|
||||
response_headers[parts[0].strip_edges()] = parts[1].strip_edges()
|
||||
|
||||
var response_body = body.get_string_from_utf8()
|
||||
|
||||
if result != HTTPRequest.RESULT_SUCCESS or response_code != 200:
|
||||
print("Failed to fetch text resource. URL: ", url, " Result: ", result, " Response code: ", response_code)
|
||||
NetworkManager.complete_request(network_request.id, response_code, "Request failed", response_headers, response_body)
|
||||
return ""
|
||||
|
||||
return body.get_string_from_utf8()
|
||||
NetworkManager.complete_request(network_request.id, response_code, "OK", response_headers, response_body)
|
||||
|
||||
return response_body
|
||||
|
||||
func fetch_external_resource(url: String, base_url: String = "") -> String:
|
||||
var resolved_url = URLUtils.resolve_url(base_url, url)
|
||||
@@ -118,13 +153,15 @@ func fetch_gurt_resource(url: String) -> String:
|
||||
if gurt_url.contains("localhost"):
|
||||
gurt_url = gurt_url.replace("localhost", "127.0.0.1")
|
||||
|
||||
var network_request = NetworkManager.start_request(gurt_url, "GET", false)
|
||||
|
||||
var client = GurtProtocolClient.new()
|
||||
|
||||
for ca_cert in CertificateManager.trusted_ca_certificates:
|
||||
client.add_ca_certificate(ca_cert)
|
||||
|
||||
if not client.create_client(30):
|
||||
print("GURT resource error: Failed to create client")
|
||||
if not client.create_client_with_dns(30, GurtProtocol.DNS_SERVER_IP, GurtProtocol.DNS_SERVER_PORT):
|
||||
NetworkManager.fail_request(network_request.id, "Failed to create GURT client")
|
||||
return ""
|
||||
|
||||
var host_domain = gurt_url
|
||||
@@ -142,9 +179,16 @@ func fetch_gurt_resource(url: String) -> String:
|
||||
|
||||
if not response or not response.is_success:
|
||||
var error_msg = "Failed to load GURT resource"
|
||||
var status_code = 0
|
||||
if response:
|
||||
status_code = response.status_code
|
||||
error_msg += ": " + str(response.status_code) + " " + response.status_message
|
||||
print("GURT resource error: ", error_msg)
|
||||
NetworkManager.complete_request(network_request.id, status_code, error_msg, {}, "")
|
||||
return ""
|
||||
|
||||
return response.body.get_string_from_utf8()
|
||||
var response_headers = response.headers if response.headers else {}
|
||||
var response_body = response.body.get_string_from_utf8()
|
||||
|
||||
NetworkManager.complete_request(network_request.id, response.status_code, "OK", response_headers, response_body)
|
||||
|
||||
return response_body
|
||||
|
||||
124
flumi/Scripts/NetworkManager.gd
Normal file
124
flumi/Scripts/NetworkManager.gd
Normal file
@@ -0,0 +1,124 @@
|
||||
extends Node
|
||||
|
||||
signal request_started(request: NetworkRequest)
|
||||
signal request_completed(request: NetworkRequest)
|
||||
signal request_failed(request: NetworkRequest)
|
||||
|
||||
var active_requests: Dictionary = {} # request_id -> NetworkRequest
|
||||
var all_requests: Array[NetworkRequest] = []
|
||||
var dev_tools_network_tab: NetworkTab = null
|
||||
|
||||
func register_dev_tools_network_tab(network_tab: NetworkTab):
|
||||
dev_tools_network_tab = network_tab
|
||||
|
||||
func start_request(url: String, method: String = "GET", is_from_lua: bool = false) -> NetworkRequest:
|
||||
var request = NetworkRequest.new(url, method)
|
||||
request.is_from_lua = is_from_lua
|
||||
|
||||
active_requests[request.id] = request
|
||||
all_requests.append(request)
|
||||
|
||||
# Notify dev tools
|
||||
if dev_tools_network_tab:
|
||||
dev_tools_network_tab.add_network_request(request)
|
||||
|
||||
request_started.emit(request)
|
||||
return request
|
||||
|
||||
func complete_request(request_id: String, status_code: int, status_text: String, headers: Dictionary, body: String, body_bytes: PackedByteArray = []):
|
||||
var request = active_requests.get(request_id)
|
||||
if not request:
|
||||
return
|
||||
|
||||
request.set_response(status_code, status_text, headers, body, body_bytes)
|
||||
active_requests.erase(request_id)
|
||||
|
||||
# Update dev tools UI
|
||||
if dev_tools_network_tab:
|
||||
dev_tools_network_tab.update_request_item(request)
|
||||
|
||||
if request.status == NetworkRequest.RequestStatus.SUCCESS:
|
||||
request_completed.emit(request)
|
||||
else:
|
||||
request_failed.emit(request)
|
||||
|
||||
func fail_request(request_id: String, error_message: String):
|
||||
var request = active_requests.get(request_id)
|
||||
if not request:
|
||||
return
|
||||
|
||||
request.set_error(error_message)
|
||||
active_requests.erase(request_id)
|
||||
|
||||
# Update dev tools UI
|
||||
if dev_tools_network_tab:
|
||||
dev_tools_network_tab.update_request_item(request)
|
||||
|
||||
request_failed.emit(request)
|
||||
|
||||
func set_request_headers(request_id: String, headers: Dictionary):
|
||||
var request = active_requests.get(request_id)
|
||||
if request:
|
||||
request.request_headers = headers
|
||||
|
||||
func set_request_body(request_id: String, body: String):
|
||||
var request = active_requests.get(request_id)
|
||||
if request:
|
||||
request.request_body = body
|
||||
|
||||
func get_all_requests() -> Array[NetworkRequest]:
|
||||
return all_requests
|
||||
|
||||
func clear_all_requests():
|
||||
active_requests.clear()
|
||||
all_requests.clear()
|
||||
|
||||
if dev_tools_network_tab:
|
||||
dev_tools_network_tab.clear_all_requests()
|
||||
|
||||
func clear_all_requests_except(preserve_request_id: String):
|
||||
# Remove from active_requests but preserve specific request
|
||||
var preserved_active = null
|
||||
if active_requests.has(preserve_request_id):
|
||||
preserved_active = active_requests[preserve_request_id]
|
||||
|
||||
active_requests.clear()
|
||||
if preserved_active:
|
||||
active_requests[preserve_request_id] = preserved_active
|
||||
|
||||
# Remove from all_requests but preserve specific request
|
||||
var preserved_request = null
|
||||
for request in all_requests:
|
||||
if request.id == preserve_request_id:
|
||||
preserved_request = request
|
||||
break
|
||||
|
||||
all_requests.clear()
|
||||
if preserved_request:
|
||||
all_requests.append(preserved_request)
|
||||
|
||||
if dev_tools_network_tab:
|
||||
dev_tools_network_tab.clear_all_requests_except(preserve_request_id)
|
||||
|
||||
func get_request_stats() -> Dictionary:
|
||||
var total_requests = all_requests.size()
|
||||
var total_size = 0
|
||||
var successful_requests = 0
|
||||
var failed_requests = 0
|
||||
var pending_requests = active_requests.size()
|
||||
|
||||
for request in all_requests:
|
||||
total_size += request.size
|
||||
match request.status:
|
||||
NetworkRequest.RequestStatus.SUCCESS:
|
||||
successful_requests += 1
|
||||
NetworkRequest.RequestStatus.ERROR:
|
||||
failed_requests += 1
|
||||
|
||||
return {
|
||||
"total": total_requests,
|
||||
"successful": successful_requests,
|
||||
"failed": failed_requests,
|
||||
"pending": pending_requests,
|
||||
"total_size": total_size
|
||||
}
|
||||
1
flumi/Scripts/NetworkManager.gd.uid
Normal file
1
flumi/Scripts/NetworkManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ggm1sq7h64sr
|
||||
207
flumi/Scripts/NetworkRequest.gd
Normal file
207
flumi/Scripts/NetworkRequest.gd
Normal file
@@ -0,0 +1,207 @@
|
||||
class_name NetworkRequest
|
||||
extends RefCounted
|
||||
|
||||
enum RequestType {
|
||||
FETCH,
|
||||
DOC,
|
||||
CSS,
|
||||
LUA,
|
||||
FONT,
|
||||
IMG,
|
||||
SOCKET,
|
||||
OTHER
|
||||
}
|
||||
|
||||
enum RequestStatus {
|
||||
PENDING,
|
||||
SUCCESS,
|
||||
ERROR,
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
var id: String
|
||||
var name: String
|
||||
var url: String
|
||||
var method: String
|
||||
var type: RequestType
|
||||
var status: RequestStatus
|
||||
var status_code: int
|
||||
var status_text: String
|
||||
var size: int
|
||||
var time_ms: float
|
||||
var start_time: float
|
||||
var end_time: float
|
||||
|
||||
var request_headers: Dictionary = {}
|
||||
var response_headers: Dictionary = {}
|
||||
|
||||
var request_body: String = ""
|
||||
var response_body: String = ""
|
||||
var response_body_bytes: PackedByteArray = []
|
||||
|
||||
var mime_type: String = ""
|
||||
var is_from_lua: bool = false
|
||||
|
||||
func _init(request_url: String = "", request_method: String = "GET"):
|
||||
id = generate_id()
|
||||
url = request_url
|
||||
method = request_method.to_upper()
|
||||
name = extract_name_from_url(url)
|
||||
type = determine_type_from_url(url)
|
||||
status = RequestStatus.PENDING
|
||||
status_code = 0
|
||||
status_text = ""
|
||||
size = 0
|
||||
time_ms = 0.0
|
||||
start_time = Time.get_ticks_msec()
|
||||
end_time = 0.0
|
||||
|
||||
func generate_id() -> String:
|
||||
return str(Time.get_ticks_msec()) + "_" + str(randi())
|
||||
|
||||
func extract_name_from_url(request_url: String) -> String:
|
||||
if request_url.is_empty():
|
||||
return "Unknown"
|
||||
|
||||
var parts = request_url.split("/")
|
||||
if parts.size() > 0:
|
||||
var filename = parts[-1]
|
||||
if filename.is_empty() and parts.size() > 1:
|
||||
filename = parts[-2]
|
||||
if "?" in filename:
|
||||
filename = filename.split("?")[0]
|
||||
if "#" in filename:
|
||||
filename = filename.split("#")[0]
|
||||
return filename if not filename.is_empty() else "/"
|
||||
|
||||
return request_url
|
||||
|
||||
func determine_type_from_url(request_url: String) -> RequestType:
|
||||
var lower_url = request_url.to_lower()
|
||||
|
||||
if lower_url.ends_with(".html") or lower_url.ends_with(".htm"):
|
||||
return RequestType.DOC
|
||||
elif lower_url.ends_with(".css"):
|
||||
return RequestType.CSS
|
||||
elif lower_url.ends_with(".lua") or lower_url.ends_with(".luau"):
|
||||
return RequestType.LUA
|
||||
elif lower_url.ends_with(".woff") or lower_url.ends_with(".woff2") or lower_url.ends_with(".ttf") or lower_url.ends_with(".otf"):
|
||||
return RequestType.FONT
|
||||
elif lower_url.ends_with(".png") or lower_url.ends_with(".jpg") or lower_url.ends_with(".jpeg") or lower_url.ends_with(".gif") or lower_url.ends_with(".webp") or lower_url.ends_with(".svg") or lower_url.ends_with(".bmp"):
|
||||
return RequestType.IMG
|
||||
elif lower_url.begins_with("ws://") or lower_url.begins_with("wss://"):
|
||||
return RequestType.SOCKET
|
||||
|
||||
if not mime_type.is_empty():
|
||||
var lower_mime = mime_type.to_lower()
|
||||
if lower_mime.begins_with("text/html"):
|
||||
return RequestType.DOC
|
||||
elif lower_mime.begins_with("text/css"):
|
||||
return RequestType.CSS
|
||||
elif lower_mime.begins_with("image/"):
|
||||
return RequestType.IMG
|
||||
elif lower_mime.begins_with("font/") or lower_mime == "application/font-woff" or lower_mime == "application/font-woff2":
|
||||
return RequestType.FONT
|
||||
|
||||
if is_from_lua:
|
||||
return RequestType.FETCH
|
||||
|
||||
return RequestType.OTHER
|
||||
|
||||
func set_response(response_status_code: int, response_status_text: String, response_headers_dict: Dictionary, response_body_content: String, body_bytes: PackedByteArray = []):
|
||||
end_time = Time.get_ticks_msec()
|
||||
time_ms = end_time - start_time
|
||||
|
||||
status_code = response_status_code
|
||||
status_text = response_status_text
|
||||
response_headers = response_headers_dict
|
||||
response_body = response_body_content
|
||||
response_body_bytes = body_bytes if not body_bytes.is_empty() else response_body_content.to_utf8_buffer()
|
||||
size = response_body_bytes.size()
|
||||
|
||||
for header_name in response_headers:
|
||||
if header_name.to_lower() == "content-type":
|
||||
mime_type = response_headers[header_name].split(";")[0].strip_edges()
|
||||
break
|
||||
|
||||
type = determine_type_from_url(url)
|
||||
|
||||
if response_status_code >= 200 and response_status_code < 300:
|
||||
status = RequestStatus.SUCCESS
|
||||
else:
|
||||
status = RequestStatus.ERROR
|
||||
|
||||
func set_error(error_message: String):
|
||||
end_time = Time.get_ticks_msec()
|
||||
time_ms = end_time - start_time
|
||||
status = RequestStatus.ERROR
|
||||
status_text = error_message
|
||||
|
||||
func get_status_display() -> String:
|
||||
match status:
|
||||
RequestStatus.PENDING:
|
||||
return "Pending"
|
||||
RequestStatus.SUCCESS:
|
||||
return str(status_code)
|
||||
RequestStatus.ERROR:
|
||||
return str(status_code) if status_code > 0 else "Failed"
|
||||
RequestStatus.CANCELLED:
|
||||
return "Cancelled"
|
||||
_:
|
||||
return "Unknown"
|
||||
|
||||
func get_type_display() -> String:
|
||||
match type:
|
||||
RequestType.FETCH:
|
||||
return "Fetch"
|
||||
RequestType.DOC:
|
||||
return "Doc"
|
||||
RequestType.CSS:
|
||||
return "CSS"
|
||||
RequestType.LUA:
|
||||
return "Lua"
|
||||
RequestType.FONT:
|
||||
return "Font"
|
||||
RequestType.IMG:
|
||||
return "Img"
|
||||
RequestType.SOCKET:
|
||||
return "Socket"
|
||||
RequestType.OTHER:
|
||||
return "Other"
|
||||
_:
|
||||
return "Unknown"
|
||||
|
||||
static func format_bytes(given_size: int) -> String:
|
||||
if given_size < 1024:
|
||||
return str(given_size) + " B"
|
||||
elif given_size < 1024 * 1024:
|
||||
return str(given_size / 1024) + " KB"
|
||||
else:
|
||||
return str(given_size / (1024 * 1024)) + " MB"
|
||||
|
||||
func get_time_display() -> String:
|
||||
if status == RequestStatus.PENDING:
|
||||
return "Pending"
|
||||
if time_ms < 1000:
|
||||
return str(int(time_ms)) + " ms"
|
||||
else:
|
||||
return "%.1f s" % (time_ms / 1000.0)
|
||||
|
||||
func get_icon_texture() -> Texture2D:
|
||||
match type:
|
||||
RequestType.FETCH:
|
||||
return load("res://Assets/Icons/download.svg")
|
||||
RequestType.DOC:
|
||||
return load("res://Assets/Icons/file-text.svg")
|
||||
RequestType.CSS:
|
||||
return load("res://Assets/Icons/palette.svg")
|
||||
RequestType.LUA:
|
||||
return load("res://Assets/Icons/braces.svg")
|
||||
RequestType.FONT:
|
||||
return load("res://Assets/Icons/braces.svg")
|
||||
RequestType.IMG:
|
||||
return load("res://Assets/Icons/image.svg")
|
||||
RequestType.SOCKET:
|
||||
return load("res://Assets/Icons/arrow-down-up.svg")
|
||||
_:
|
||||
return load("res://Assets/Icons/search.svg")
|
||||
1
flumi/Scripts/NetworkRequest.gd.uid
Normal file
1
flumi/Scripts/NetworkRequest.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://r6h4tvud6yne
|
||||
@@ -11,24 +11,16 @@ extends PanelContainer
|
||||
var request: NetworkRequest
|
||||
var network_tab: NetworkTab
|
||||
|
||||
@onready var normal_style: StyleBox = get_meta("normal_style")
|
||||
@onready var selected_style: StyleBox = get_meta("selected_style")
|
||||
|
||||
@onready var success_color: Color = status_label.get_meta("success_color")
|
||||
@onready var error_color: Color = status_label.get_meta("error_color")
|
||||
@onready var pending_color: Color = status_label.get_meta("pending_color")
|
||||
|
||||
signal item_clicked(request: NetworkRequest)
|
||||
|
||||
func _ready():
|
||||
# Set up styles for different states
|
||||
var style_normal = StyleBoxFlat.new()
|
||||
style_normal.bg_color = Color.TRANSPARENT
|
||||
style_normal.content_margin_left = 5
|
||||
style_normal.content_margin_bottom = 5
|
||||
style_normal.content_margin_right = 5
|
||||
style_normal.content_margin_top = 5
|
||||
style_normal.corner_radius_bottom_left = 8
|
||||
style_normal.corner_radius_bottom_right = 8
|
||||
style_normal.corner_radius_top_left = 8
|
||||
style_normal.corner_radius_top_right = 8
|
||||
|
||||
add_theme_stylebox_override("panel", style_normal)
|
||||
|
||||
# Set up mouse handling
|
||||
mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
gui_input.connect(_on_gui_input)
|
||||
|
||||
@@ -48,7 +40,7 @@ func update_display():
|
||||
name_label.text = request.name
|
||||
status_label.text = request.get_status_display()
|
||||
type_label.text = request.get_type_display()
|
||||
size_label.text = request.get_size_display()
|
||||
size_label.text = NetworkRequest.format_bytes(request.size)
|
||||
time_label.text = request.get_time_display()
|
||||
|
||||
# Color code status
|
||||
@@ -67,29 +59,9 @@ func _on_gui_input(event: InputEvent):
|
||||
|
||||
func set_selected(selected: bool):
|
||||
if selected:
|
||||
var style_selected = StyleBoxFlat.new()
|
||||
style_selected.bg_color = Color(0.2, 0.4, 0.8, 0.3)
|
||||
style_selected.content_margin_left = 5
|
||||
style_selected.content_margin_bottom = 5
|
||||
style_selected.content_margin_right = 5
|
||||
style_selected.content_margin_top = 5
|
||||
style_selected.corner_radius_bottom_left = 8
|
||||
style_selected.corner_radius_bottom_right = 8
|
||||
style_selected.corner_radius_top_left = 8
|
||||
style_selected.corner_radius_top_right = 8
|
||||
add_theme_stylebox_override("panel", style_selected)
|
||||
add_theme_stylebox_override("panel", selected_style)
|
||||
else:
|
||||
var style_normal = StyleBoxFlat.new()
|
||||
style_normal.bg_color = Color.TRANSPARENT
|
||||
style_normal.content_margin_left = 5
|
||||
style_normal.content_margin_bottom = 5
|
||||
style_normal.content_margin_right = 5
|
||||
style_normal.content_margin_top = 5
|
||||
style_normal.corner_radius_bottom_left = 8
|
||||
style_normal.corner_radius_bottom_right = 8
|
||||
style_normal.corner_radius_top_left = 8
|
||||
style_normal.corner_radius_top_right = 8
|
||||
add_theme_stylebox_override("panel", style_normal)
|
||||
add_theme_stylebox_override("panel", normal_style)
|
||||
|
||||
func hide_columns(should_hide: bool):
|
||||
status_label.visible = !should_hide
|
||||
|
||||
1
flumi/Scripts/NetworkRequestItem.gd.uid
Normal file
1
flumi/Scripts/NetworkRequestItem.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bcs7m624uvv3x
|
||||
398
flumi/Scripts/NetworkTab.gd
Normal file
398
flumi/Scripts/NetworkTab.gd
Normal file
@@ -0,0 +1,398 @@
|
||||
class_name NetworkTab
|
||||
extends VBoxContainer
|
||||
|
||||
const NetworkRequestItemScene = preload("res://Scenes/NetworkRequestItem.tscn")
|
||||
|
||||
@onready var filter_dropdown: OptionButton = %FilterDropdown
|
||||
|
||||
# Details panel components
|
||||
@onready var close_button: Button = %CloseButton
|
||||
@onready var details_tab_container: TabContainer = $MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer
|
||||
@onready var headers_tab: VBoxContainer = $MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer/Headers
|
||||
@onready var preview_tab: Control = $MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer/Preview
|
||||
@onready var response_tab: Control = $MainContainer/RightPanel/PanelContainer/HBoxContainer/TabContainer/Response
|
||||
|
||||
# Header components
|
||||
@onready var status_header: Label = %StatusHeader
|
||||
@onready var type_header: Label = %TypeHeader
|
||||
@onready var size_header: Label = %SizeHeader
|
||||
@onready var time_header: Label = %TimeHeader
|
||||
|
||||
# Main components
|
||||
@onready var main_container: HSplitContainer = $MainContainer
|
||||
@onready var request_list: VBoxContainer = $MainContainer/LeftPanel/ScrollContainer/RequestList
|
||||
@onready var scroll_container: ScrollContainer = $MainContainer/LeftPanel/ScrollContainer
|
||||
@onready var details_panel: Control = $MainContainer/RightPanel
|
||||
@onready var status_bar: HBoxContainer = $HBoxContainer/StatusBar
|
||||
@onready var request_count_label: Label = $HBoxContainer/StatusBar/RequestCount
|
||||
@onready var transfer_label: Label = $HBoxContainer/StatusBar/Transfer
|
||||
@onready var loaded_label: Label = $HBoxContainer/StatusBar/Loaded
|
||||
|
||||
@onready var syntax_highlighter = preload("res://Resources/LuaSyntaxHighlighter.tres")
|
||||
|
||||
var network_requests: Array[NetworkRequest] = []
|
||||
var current_filter: NetworkRequest.RequestType = -1 # -1 means all
|
||||
var selected_request: NetworkRequest = null
|
||||
var request_items: Dictionary = {}
|
||||
|
||||
signal request_selected(request: NetworkRequest)
|
||||
|
||||
func _ready():
|
||||
details_panel.visible = false
|
||||
|
||||
if main_container and main_container.size.x > 0:
|
||||
main_container.split_offset = int(main_container.size.x)
|
||||
|
||||
update_status_bar()
|
||||
|
||||
NetworkManager.register_dev_tools_network_tab(self)
|
||||
|
||||
func add_network_request(request: NetworkRequest):
|
||||
network_requests.append(request)
|
||||
|
||||
var request_item = NetworkRequestItemScene.instantiate() as NetworkRequestItem
|
||||
request_list.add_child(request_item)
|
||||
request_item.init(request, self)
|
||||
request_item.item_clicked.connect(_on_request_item_clicked)
|
||||
|
||||
request_items[request.id] = request_item
|
||||
|
||||
apply_filter()
|
||||
update_status_bar()
|
||||
|
||||
func apply_filter():
|
||||
for request in network_requests:
|
||||
var item = request_items.get(request.id)
|
||||
if item:
|
||||
var should_show = (current_filter == -1) or (request.type == current_filter)
|
||||
item.visible = should_show
|
||||
|
||||
func update_request_item(request: NetworkRequest):
|
||||
var request_item = request_items.get(request.id) as NetworkRequestItem
|
||||
if not request_item:
|
||||
return
|
||||
|
||||
request_item.update_display()
|
||||
|
||||
apply_filter()
|
||||
update_status_bar()
|
||||
|
||||
func update_details_panel(request: NetworkRequest):
|
||||
clear_details_panel()
|
||||
update_headers_tab(request)
|
||||
update_preview_tab(request)
|
||||
update_response_tab(request)
|
||||
|
||||
func clear_details_panel():
|
||||
for child in headers_tab.get_children(): child.queue_free()
|
||||
for child in preview_tab.get_children(): child.queue_free()
|
||||
for child in response_tab.get_children(): child.queue_free()
|
||||
|
||||
func create_collapsible_section(title: String, expanded: bool = false) -> VBoxContainer:
|
||||
var section = VBoxContainer.new()
|
||||
|
||||
# Header w/ toggle button
|
||||
var header = HBoxContainer.new()
|
||||
header.custom_minimum_size.y = 28
|
||||
|
||||
var toggle_button = Button.new()
|
||||
toggle_button.text = "▼" if expanded else "▶"
|
||||
toggle_button.custom_minimum_size = Vector2(20, 20)
|
||||
toggle_button.flat = true
|
||||
toggle_button.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||||
header.add_child(toggle_button)
|
||||
|
||||
var title_label = Label.new()
|
||||
title_label.text = title
|
||||
title_label.add_theme_font_size_override("font_size", 14)
|
||||
title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
header.add_child(title_label)
|
||||
|
||||
section.add_child(header)
|
||||
|
||||
# Content container
|
||||
var content = VBoxContainer.new()
|
||||
content.visible = expanded
|
||||
section.add_child(content)
|
||||
|
||||
toggle_button.pressed.connect(func():
|
||||
content.visible = !content.visible
|
||||
toggle_button.text = "▼" if content.visible else "▶"
|
||||
)
|
||||
|
||||
return section
|
||||
|
||||
func add_header_row(parent: VBoxContainer, header_name: String, value: String):
|
||||
var row = HBoxContainer.new()
|
||||
|
||||
var name_label = Label.new()
|
||||
name_label.text = header_name
|
||||
name_label.custom_minimum_size.x = 200
|
||||
name_label.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
name_label.vertical_alignment = VERTICAL_ALIGNMENT_TOP
|
||||
name_label.clip_text = true
|
||||
name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
row.add_child(name_label)
|
||||
|
||||
var value_label = Label.new()
|
||||
value_label.text = value
|
||||
value_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
value_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
value_label.vertical_alignment = VERTICAL_ALIGNMENT_TOP
|
||||
row.add_child(value_label)
|
||||
|
||||
parent.add_child(row)
|
||||
|
||||
func update_headers_tab(request: NetworkRequest):
|
||||
var general_section = create_collapsible_section("General", true)
|
||||
headers_tab.add_child(general_section)
|
||||
|
||||
var general_content = general_section.get_child(1)
|
||||
add_header_row(general_content, "Request URL:", request.url)
|
||||
add_header_row(general_content, "Request Method:", request.method)
|
||||
add_header_row(general_content, "Status Code:", str(request.status_code) + " " + request.status_text)
|
||||
|
||||
# Request Headers section
|
||||
if not request.request_headers.is_empty():
|
||||
var request_headers_section = create_collapsible_section("Request Headers", false)
|
||||
headers_tab.add_child(request_headers_section)
|
||||
|
||||
var request_headers_content = request_headers_section.get_child(1)
|
||||
for header_name in request.request_headers:
|
||||
add_header_row(request_headers_content, header_name + ":", str(request.request_headers[header_name]))
|
||||
|
||||
# Response Headers section
|
||||
if not request.response_headers.is_empty():
|
||||
var response_headers_section = create_collapsible_section("Response Headers", false)
|
||||
headers_tab.add_child(response_headers_section)
|
||||
|
||||
var response_headers_content = response_headers_section.get_child(1)
|
||||
for header_name in request.response_headers:
|
||||
add_header_row(response_headers_content, header_name + ":", str(request.response_headers[header_name]))
|
||||
|
||||
func update_preview_tab(request: NetworkRequest):
|
||||
# For images, show the image in the preview tab
|
||||
if request.type == NetworkRequest.RequestType.IMG and request.status == NetworkRequest.RequestStatus.SUCCESS:
|
||||
var image = Image.new()
|
||||
var response_bytes = request.response_body_bytes
|
||||
var load_error = ERR_UNAVAILABLE
|
||||
|
||||
load_error = image.load_png_from_buffer(response_bytes)
|
||||
if load_error != OK:
|
||||
load_error = image.load_jpg_from_buffer(response_bytes)
|
||||
if load_error != OK:
|
||||
load_error = image.load_webp_from_buffer(response_bytes)
|
||||
if load_error != OK:
|
||||
load_error = image.load_bmp_from_buffer(response_bytes)
|
||||
if load_error != OK:
|
||||
load_error = image.load_tga_from_buffer(response_bytes)
|
||||
|
||||
if load_error == OK:
|
||||
var texture = ImageTexture.create_from_image(image)
|
||||
|
||||
var container = VBoxContainer.new()
|
||||
container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
container.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
|
||||
var texture_rect = TextureRect.new()
|
||||
texture_rect.texture = texture
|
||||
texture_rect.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
|
||||
texture_rect.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
texture_rect.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
|
||||
var img_size = image.get_size()
|
||||
var max_size = 200.0
|
||||
var scale_factor = min(max_size / img_size.x, max_size / img_size.y, 1.0)
|
||||
texture_rect.custom_minimum_size = Vector2(img_size.x * scale_factor, img_size.y * scale_factor)
|
||||
|
||||
container.add_child(texture_rect)
|
||||
preview_tab.add_child(container)
|
||||
return
|
||||
else:
|
||||
var label = Label.new()
|
||||
label.text = "Failed to load image data (Error: " + str(load_error) + ")"
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
preview_tab.add_child(label)
|
||||
return
|
||||
|
||||
# For non-images, show request body
|
||||
if request.request_body.is_empty():
|
||||
var label = Label.new()
|
||||
label.text = "No request body"
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
preview_tab.add_child(label)
|
||||
return
|
||||
|
||||
# CodeEdit for request body
|
||||
# TODO: Syntax highlight based on Content-Type, we need a JSON, HTML and CSS highlighter too
|
||||
var code_edit = CodeEditUtils.create_code_edit({
|
||||
"text": request.request_body,
|
||||
"editable": false,
|
||||
"show_line_numbers": true,
|
||||
"syntax_highlighter": syntax_highlighter.duplicate()
|
||||
})
|
||||
|
||||
preview_tab.add_child(code_edit)
|
||||
|
||||
func update_response_tab(request: NetworkRequest):
|
||||
if request.type == NetworkRequest.RequestType.IMG:
|
||||
var label = Label.new()
|
||||
label.text = "This response contains image data. See the \"Preview\" tab to view the image."
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
response_tab.add_child(label)
|
||||
return
|
||||
|
||||
if request.response_body.is_empty():
|
||||
var label = Label.new()
|
||||
label.text = "No response body"
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
response_tab.add_child(label)
|
||||
return
|
||||
|
||||
# Check if we can display the content
|
||||
var can_display = true
|
||||
if request.mime_type.begins_with("video/") or request.mime_type.begins_with("audio/"):
|
||||
can_display = false
|
||||
|
||||
if not can_display:
|
||||
var label = Label.new()
|
||||
label.text = "Cannot preview this content type: " + request.mime_type
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
response_tab.add_child(label)
|
||||
return
|
||||
|
||||
# Create CodeEdit for response body
|
||||
var code_edit = CodeEditUtils.create_code_edit({
|
||||
"text": request.response_body,
|
||||
"editable": false,
|
||||
"show_line_numbers": true,
|
||||
"syntax_highlighter": syntax_highlighter.duplicate()
|
||||
})
|
||||
|
||||
response_tab.add_child(code_edit)
|
||||
|
||||
func update_status_bar():
|
||||
var total_requests = network_requests.size()
|
||||
var total_size = 0
|
||||
var loaded_resources = 0
|
||||
|
||||
for request in network_requests:
|
||||
total_size += request.size
|
||||
if request.status == NetworkRequest.RequestStatus.SUCCESS:
|
||||
loaded_resources += 1
|
||||
|
||||
request_count_label.text = str(total_requests) + " requests"
|
||||
transfer_label.text = NetworkRequest.format_bytes(total_size) + " transferred"
|
||||
loaded_label.text = str(loaded_resources) + " resources loaded"
|
||||
|
||||
func hide_details_panel():
|
||||
# Hide details panel and show columns again
|
||||
details_panel.visible = false
|
||||
hide_columns(false)
|
||||
selected_request = null
|
||||
if main_container.size.x > 0:
|
||||
main_container.split_offset = int(main_container.size.x)
|
||||
|
||||
# Clear selection visual
|
||||
for req_id in request_items:
|
||||
var request_item = request_items[req_id] as NetworkRequestItem
|
||||
request_item.set_selected(false)
|
||||
|
||||
func hide_columns(should_hide: bool):
|
||||
# Hide/show header labels
|
||||
status_header.visible = !should_hide
|
||||
type_header.visible = !should_hide
|
||||
size_header.visible = !should_hide
|
||||
time_header.visible = !should_hide
|
||||
|
||||
# Hide/show status, type, size, time columns for all request items
|
||||
for request_item in request_items.values():
|
||||
var network_request_item = request_item as NetworkRequestItem
|
||||
network_request_item.hide_columns(should_hide)
|
||||
|
||||
func clear_all_requests():
|
||||
for item in request_items.values():
|
||||
item.queue_free()
|
||||
|
||||
network_requests.clear()
|
||||
request_items.clear()
|
||||
selected_request = null
|
||||
|
||||
# Hide details panel and show columns again
|
||||
details_panel.visible = false
|
||||
hide_columns(false)
|
||||
if main_container.size.x > 0:
|
||||
main_container.split_offset = int(main_container.size.x)
|
||||
|
||||
update_status_bar()
|
||||
|
||||
func clear_all_requests_except(preserve_request_id: String):
|
||||
# Remove all items except the preserved one
|
||||
var preserved_request = null
|
||||
var preserved_item = null
|
||||
|
||||
for request in network_requests:
|
||||
if request.id == preserve_request_id:
|
||||
preserved_request = request
|
||||
preserved_item = request_items.get(preserve_request_id)
|
||||
break
|
||||
|
||||
# Clear all items except preserved one
|
||||
for item_id in request_items:
|
||||
if item_id != preserve_request_id:
|
||||
var item = request_items[item_id]
|
||||
item.queue_free()
|
||||
|
||||
network_requests.clear()
|
||||
request_items.clear()
|
||||
|
||||
# Re-add preserved request and item
|
||||
if preserved_request and preserved_item:
|
||||
network_requests.append(preserved_request)
|
||||
request_items[preserve_request_id] = preserved_item
|
||||
|
||||
selected_request = null
|
||||
|
||||
# Hide details panel and show columns again
|
||||
details_panel.visible = false
|
||||
hide_columns(false)
|
||||
if main_container.size.x > 0:
|
||||
main_container.split_offset = int(main_container.size.x)
|
||||
|
||||
update_status_bar()
|
||||
|
||||
func _on_request_item_clicked(request: NetworkRequest):
|
||||
if selected_request == request:
|
||||
hide_details_panel()
|
||||
return
|
||||
|
||||
selected_request = request
|
||||
request_selected.emit(request)
|
||||
|
||||
for req_id in request_items:
|
||||
var request_item = request_items[req_id] as NetworkRequestItem
|
||||
request_item.set_selected(req_id == request.id)
|
||||
|
||||
details_panel.visible = true
|
||||
if main_container.size.x > 0:
|
||||
# Give 6/8 (3/4) of space to details panel, 2/8 (1/4) to left panel
|
||||
main_container.split_offset = int(main_container.size.x * 0.25)
|
||||
|
||||
hide_columns(true)
|
||||
update_details_panel(request)
|
||||
|
||||
func _on_filter_selected(index: int):
|
||||
var filter_type = index - 1 # 0 -> -1 (All), 1 -> 0 (Fetch)...
|
||||
|
||||
if current_filter == filter_type:
|
||||
return
|
||||
|
||||
current_filter = filter_type
|
||||
apply_filter()
|
||||
1
flumi/Scripts/NetworkTab.gd.uid
Normal file
1
flumi/Scripts/NetworkTab.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dh3jdrot4r7m3
|
||||
@@ -35,7 +35,7 @@ var loading_tween: Tween
|
||||
var scroll_container: ScrollContainer = null
|
||||
var website_container: VBoxContainer = null
|
||||
var background_panel: PanelContainer = null
|
||||
var main_hbox: HBoxContainer = null
|
||||
var main_hbox: HSplitContainer = null
|
||||
var dev_tools: Control = null
|
||||
var dev_tools_visible: bool = false
|
||||
var lua_apis: Array[LuaAPI] = []
|
||||
@@ -150,7 +150,7 @@ func init_scene(parent_container: Control) -> void:
|
||||
style_box.bg_color = Color(1, 1, 1, 1) # White background
|
||||
background_panel.add_theme_stylebox_override("panel", style_box)
|
||||
|
||||
main_hbox = HBoxContainer.new()
|
||||
main_hbox = HSplitContainer.new()
|
||||
main_hbox.name = "Tab_MainHBox_" + str(get_instance_id())
|
||||
main_hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
main_hbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
|
||||
94
flumi/Scripts/Utils/CodeEditUtils.gd
Normal file
94
flumi/Scripts/Utils/CodeEditUtils.gd
Normal file
@@ -0,0 +1,94 @@
|
||||
class_name CodeEditUtils
|
||||
|
||||
static func create_code_edit(options: Dictionary = {}) -> CodeEdit:
|
||||
var code_edit = CodeEdit.new()
|
||||
|
||||
# Default configuration
|
||||
var defaults = {
|
||||
"text": "",
|
||||
"editable": true,
|
||||
"size_flags_horizontal": Control.SIZE_EXPAND_FILL,
|
||||
"size_flags_vertical": Control.SIZE_EXPAND_FILL,
|
||||
"scroll_fit_content_height": false,
|
||||
"context_menu_enabled": true,
|
||||
"shortcut_keys_enabled": true,
|
||||
"selecting_enabled": true,
|
||||
"deselect_on_focus_loss_enabled": true,
|
||||
"drag_and_drop_selection_enabled": false,
|
||||
"virtual_keyboard_enabled": false,
|
||||
"middle_mouse_paste_enabled": false,
|
||||
"show_line_numbers": false,
|
||||
"syntax_highlighter": null,
|
||||
"transparent_background": false,
|
||||
"block_editing_signals": false
|
||||
}
|
||||
|
||||
# Merge user options with defaults
|
||||
for key in defaults:
|
||||
if options.has(key):
|
||||
defaults[key] = options[key]
|
||||
|
||||
# Apply basic properties
|
||||
code_edit.text = defaults.text
|
||||
code_edit.size_flags_horizontal = defaults.size_flags_horizontal
|
||||
code_edit.size_flags_vertical = defaults.size_flags_vertical
|
||||
code_edit.scroll_fit_content_height = defaults.scroll_fit_content_height
|
||||
code_edit.context_menu_enabled = defaults.context_menu_enabled
|
||||
code_edit.shortcut_keys_enabled = defaults.shortcut_keys_enabled
|
||||
code_edit.selecting_enabled = defaults.selecting_enabled
|
||||
code_edit.deselect_on_focus_loss_enabled = defaults.deselect_on_focus_loss_enabled
|
||||
code_edit.drag_and_drop_selection_enabled = defaults.drag_and_drop_selection_enabled
|
||||
code_edit.virtual_keyboard_enabled = defaults.virtual_keyboard_enabled
|
||||
code_edit.middle_mouse_paste_enabled = defaults.middle_mouse_paste_enabled
|
||||
|
||||
# Line numbers
|
||||
if defaults.show_line_numbers:
|
||||
code_edit.gutters_draw_line_numbers = true
|
||||
|
||||
# Syntax highlighter
|
||||
if defaults.syntax_highlighter:
|
||||
code_edit.syntax_highlighter = defaults.syntax_highlighter
|
||||
|
||||
# Transparent background styling
|
||||
if defaults.transparent_background:
|
||||
var code_style_normal = StyleBoxFlat.new()
|
||||
code_style_normal.bg_color = Color.TRANSPARENT
|
||||
code_style_normal.border_width_left = 0
|
||||
code_style_normal.border_width_top = 0
|
||||
code_style_normal.border_width_right = 0
|
||||
code_style_normal.border_width_bottom = 0
|
||||
code_style_normal.content_margin_bottom = 8
|
||||
code_edit.add_theme_stylebox_override("normal", code_style_normal)
|
||||
code_edit.add_theme_stylebox_override("focus", code_style_normal)
|
||||
|
||||
# Block editing
|
||||
# This is because Godot applies some transparency when we simply set editable=false, which I cant be bothered to fix
|
||||
if defaults.block_editing_signals and defaults.editable:
|
||||
code_edit.gui_input.connect(_block_editing_input)
|
||||
|
||||
return code_edit
|
||||
|
||||
static func _block_editing_input(event: InputEvent):
|
||||
# Block text modification events while allowing selection and copy
|
||||
if event is InputEventKey:
|
||||
var key_event = event as InputEventKey
|
||||
# Allow Ctrl+C (copy), Ctrl+A (select all), arrow keys, etc.
|
||||
if key_event.pressed:
|
||||
# Allow copy operations
|
||||
if key_event.ctrl_pressed and key_event.keycode == KEY_C:
|
||||
return
|
||||
if key_event.ctrl_pressed and key_event.keycode == KEY_A:
|
||||
return
|
||||
# Allow navigation
|
||||
if key_event.keycode in [KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_HOME, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN]:
|
||||
return
|
||||
# Block all other key inputs
|
||||
if key_event.keycode != KEY_ESCAPE:
|
||||
event.set_input_as_handled()
|
||||
|
||||
static func create_readonly_code_edit(text: String, options: Dictionary = {}) -> CodeEdit:
|
||||
var readonly_options = options.duplicate()
|
||||
readonly_options["text"] = text
|
||||
readonly_options["editable"] = false
|
||||
readonly_options["block_editing_signals"] = false
|
||||
return create_code_edit(readonly_options)
|
||||
1
flumi/Scripts/Utils/CodeEditUtils.gd.uid
Normal file
1
flumi/Scripts/Utils/CodeEditUtils.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cc7m0rc5oxjnv
|
||||
@@ -29,14 +29,14 @@ static func resolve_fetch_url(url: String) -> String:
|
||||
return URLUtils.resolve_url(current_domain, url)
|
||||
|
||||
static func _lua_fetch_handler(vm: LuauVM) -> int:
|
||||
var url: String = vm.luaL_checkstring(1)
|
||||
var original_url: String = vm.luaL_checkstring(1)
|
||||
var options: Dictionary = {}
|
||||
|
||||
if vm.lua_gettop() >= 2 and vm.lua_istable(2):
|
||||
options = vm.lua_todictionary(2)
|
||||
|
||||
# Resolve relative URLs and default to gurt:// protocol
|
||||
url = resolve_fetch_url(url)
|
||||
var url = resolve_fetch_url(original_url)
|
||||
|
||||
# Default options
|
||||
var method = options.get("method", "GET").to_upper()
|
||||
@@ -296,7 +296,7 @@ static func get_or_create_gurt_client(domain: String) -> GurtProtocolClient:
|
||||
for ca_cert in CertificateManager.trusted_ca_certificates:
|
||||
gurt_client.add_ca_certificate(ca_cert)
|
||||
|
||||
if not gurt_client.create_client(10):
|
||||
if not gurt_client.create_client_with_dns(10, GurtProtocol.DNS_SERVER_IP, GurtProtocol.DNS_SERVER_PORT):
|
||||
gurt_client = null
|
||||
current_domain = ""
|
||||
return null
|
||||
@@ -316,8 +316,6 @@ static func make_gurt_request(url: String, method: String, headers: PackedString
|
||||
var domain_part = url.replace("gurt://", "")
|
||||
if domain_part.contains("/"):
|
||||
domain_part = domain_part.split("/")[0]
|
||||
if domain_part.contains(":"):
|
||||
domain_part = domain_part.split(":")[0]
|
||||
|
||||
var client = get_or_create_gurt_client(domain_part)
|
||||
if client == null:
|
||||
|
||||
@@ -36,6 +36,9 @@ const MIN_SIZE = Vector2i(750, 200)
|
||||
|
||||
var font_dependent_elements: Array = []
|
||||
var current_domain = ""
|
||||
var main_navigation_request: NetworkRequest = null
|
||||
var network_start_time: float = 0.0
|
||||
var network_end_time: float = 0.0
|
||||
|
||||
func should_group_as_inline(element: HTMLParser.HTMLElement) -> bool:
|
||||
if element.tag_name == "input":
|
||||
@@ -61,7 +64,7 @@ func _ready():
|
||||
|
||||
call_deferred("render")
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
func _input(_event: InputEvent) -> void:
|
||||
if Input.is_action_just_pressed("DevTools"):
|
||||
_toggle_dev_tools()
|
||||
get_viewport().set_input_as_handled()
|
||||
@@ -104,6 +107,10 @@ func _on_search_submitted(url: String) -> void:
|
||||
print("Non-GURT URL entered: ", url)
|
||||
|
||||
func fetch_gurt_content_async(gurt_url: String, tab: Tab, original_url: String) -> void:
|
||||
main_navigation_request = NetworkManager.start_request(gurt_url, "GET", false)
|
||||
main_navigation_request.type = NetworkRequest.RequestType.DOC
|
||||
network_start_time = Time.get_ticks_msec()
|
||||
|
||||
var thread = Thread.new()
|
||||
var request_data = {"gurt_url": gurt_url}
|
||||
|
||||
@@ -125,7 +132,7 @@ func _perform_gurt_request_threaded(request_data: Dictionary) -> Dictionary:
|
||||
|
||||
if not client.create_client_with_dns(30, GurtProtocol.DNS_SERVER_IP, GurtProtocol.DNS_SERVER_PORT):
|
||||
client.disconnect()
|
||||
return {"success": false, "error": "Failed to connect to GURT DNS server"}
|
||||
return {"success": false, "error": "Failed to connect to GURT DNS server at " + GurtProtocol.DNS_SERVER_IP + ":" + str(GurtProtocol.DNS_SERVER_PORT)}
|
||||
|
||||
var response = client.request(gurt_url, {
|
||||
"method": "GET"
|
||||
@@ -136,7 +143,7 @@ func _perform_gurt_request_threaded(request_data: Dictionary) -> Dictionary:
|
||||
var error_msg = "Connection failed"
|
||||
if response:
|
||||
error_msg = "GURT %d: %s" % [response.status_code, response.status_message]
|
||||
elif not response:
|
||||
else:
|
||||
error_msg = "Request timed out or connection failed"
|
||||
return {"success": false, "error": error_msg}
|
||||
|
||||
@@ -149,6 +156,7 @@ func _handle_gurt_result(result: Dictionary, tab: Tab, original_url: String, gur
|
||||
return
|
||||
|
||||
var html_bytes = result.html_bytes
|
||||
network_end_time = Time.get_ticks_msec()
|
||||
|
||||
current_domain = gurt_url
|
||||
if not search_bar.has_focus():
|
||||
@@ -156,6 +164,14 @@ func _handle_gurt_result(result: Dictionary, tab: Tab, original_url: String, gur
|
||||
|
||||
render_content(html_bytes)
|
||||
|
||||
if main_navigation_request:
|
||||
main_navigation_request.end_time = network_end_time
|
||||
main_navigation_request.time_ms = network_end_time - network_start_time
|
||||
var headers = {"content-type": "text/html"}
|
||||
var body_text = html_bytes.get_string_from_utf8()
|
||||
NetworkManager.complete_request(main_navigation_request.id, 200, "OK", headers, body_text, html_bytes)
|
||||
main_navigation_request = null
|
||||
|
||||
tab.stop_loading()
|
||||
|
||||
func handle_gurt_error(error_message: String, tab: Tab) -> void:
|
||||
@@ -182,6 +198,11 @@ func render() -> void:
|
||||
render_content(Constants.HTML_CONTENT)
|
||||
|
||||
func render_content(html_bytes: PackedByteArray) -> void:
|
||||
if main_navigation_request:
|
||||
NetworkManager.clear_all_requests_except(main_navigation_request.id)
|
||||
else:
|
||||
NetworkManager.clear_all_requests()
|
||||
|
||||
var active_tab = get_active_tab()
|
||||
var target_container: Control
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ config/icon="uid://ctpe0lbehepen"
|
||||
|
||||
Constants="*res://Scripts/Constants.gd"
|
||||
Network="*res://Scripts/Network.gd"
|
||||
NetworkManager="*res://Scripts/NetworkManager.gd"
|
||||
|
||||
[display]
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ For production deployments, you can use the Gurted Certificate Authority to get
|
||||
|
||||
3. **Follow the DNS challenge instructions:**
|
||||
When prompted, add the TXT record to your domain:
|
||||
- Go to gurt://localhost:8877 (or your DNS server)
|
||||
- Go to gurt://dns.web (or your DNS server)
|
||||
- Login and navigate to your domain
|
||||
- Add a TXT record with:
|
||||
- Name: `_gurtca-challenge`
|
||||
|
||||
@@ -30,7 +30,7 @@ async fn verify_dns_txt_record(domain: &str, expected_value: &str, client: &Gurt
|
||||
});
|
||||
|
||||
let response = client
|
||||
.post_json("gurt://localhost:8877/resolve-full", &request)
|
||||
.post_json("gurt://dns.web/resolve-full", &request)
|
||||
.await?;
|
||||
|
||||
if response.is_success() {
|
||||
|
||||
@@ -12,7 +12,7 @@ struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
|
||||
#[arg(long, default_value = "gurt://localhost:8877")]
|
||||
#[arg(long, default_value = "gurt://dns.web")]
|
||||
ca_url: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
-- Get UI elements
|
||||
local logArea = gurt.select('#log-area')
|
||||
local statusDisplay = gurt.select('#status-display')
|
||||
local connectBtn = gurt.select('#connect-btn')
|
||||
@@ -39,7 +38,6 @@
|
||||
local socket = nil
|
||||
local connected = false
|
||||
|
||||
-- Function to add message to log
|
||||
local function addLog(message)
|
||||
table.insert(logMessages, Time.format(Time.now(), '%H:%M:%S') .. ' - ' .. message)
|
||||
if #logMessages > 50 then
|
||||
@@ -48,17 +46,14 @@
|
||||
logArea.text = table.concat(logMessages, '\\n')
|
||||
end
|
||||
|
||||
-- Function to update status display
|
||||
local function updateStatus(status, color)
|
||||
statusDisplay.text = 'Status: ' .. status
|
||||
statusDisplay.style = 'status-' .. color
|
||||
end
|
||||
|
||||
-- Initialize with default values
|
||||
urlInput.text = 'wss://echo.websocket.org'
|
||||
messageInput.text = 'Hello from Gurted!'
|
||||
|
||||
-- Connect to WebSocket
|
||||
connectBtn:on('click', function()
|
||||
local url = urlInput.text
|
||||
addLog('Attempting to connect to: ' .. url)
|
||||
@@ -90,7 +85,6 @@
|
||||
end)
|
||||
end)
|
||||
|
||||
-- Disconnect from WebSocket
|
||||
disconnectBtn:on('click', function()
|
||||
if socket and connected then
|
||||
addLog('Disconnecting from WebSocket...')
|
||||
@@ -100,7 +94,6 @@
|
||||
end
|
||||
end)
|
||||
|
||||
-- Send message
|
||||
sendBtn:on('click', function()
|
||||
if socket and connected then
|
||||
local message = messageInput.text
|
||||
@@ -115,14 +108,12 @@
|
||||
end
|
||||
end)
|
||||
|
||||
-- Clear log button
|
||||
clearLogBtn:on('click', function()
|
||||
logMessages = {}
|
||||
logArea.text = 'Log cleared.'
|
||||
addLog('WebSocket API demo ready')
|
||||
end)
|
||||
|
||||
-- Allow Enter key to send messages
|
||||
messageInput:on('keypress', function(e)
|
||||
if e.key == 'Enter' and socket and connected then
|
||||
local message = messageInput.text
|
||||
@@ -134,7 +125,6 @@
|
||||
end
|
||||
end)
|
||||
|
||||
-- Initialize
|
||||
updateStatus('Disconnected', 'disconnected')
|
||||
addLog('WebSocket API demo ready')
|
||||
addLog('Enter a WebSocket URL and click Connect to start!')
|
||||
|
||||
Reference in New Issue
Block a user