mirror of
https://github.com/l5yth/potato-mesh.git
synced 2026-08-08 01:42:59 +02:00
web: fix federation for multi protocol (#722)
* web: fix federation for multi protocol * web: fix short name emojis * web: address review comments * ci: fix the codeql gap * ci: fix the codeql gap * ci: fix the codeql gap * ci: remove swift
This commit is contained in:
@@ -23,7 +23,7 @@ on:
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
packages: read
|
||||
|
||||
@@ -297,9 +297,12 @@ module PotatoMesh
|
||||
def shutdown_federation_background_work!(timeout: nil)
|
||||
request_federation_shutdown!
|
||||
timeout_value = timeout || PotatoMesh::Config.federation_shutdown_timeout_seconds
|
||||
# Drain the worker pool first so federation threads blocked in
|
||||
# wait_for_federation_tasks unblock promptly instead of waiting
|
||||
# for each task's individual timeout to expire.
|
||||
shutdown_federation_worker_pool!
|
||||
stop_federation_thread!(:initial_federation_thread, timeout: timeout_value)
|
||||
stop_federation_thread!(:federation_thread, timeout: timeout_value)
|
||||
shutdown_federation_worker_pool!
|
||||
clear_federation_crawl_state!
|
||||
end
|
||||
|
||||
|
||||
@@ -346,3 +346,46 @@ test('createMessageChatEntry: meshtastic message with @[Name] is NOT resolved as
|
||||
assert.ok(shortNameCount <= 1, 'only the sender badge should be present, no mention badge');
|
||||
});
|
||||
});
|
||||
|
||||
// --- renderShortHtml badge padding ---
|
||||
|
||||
test('renderShortHtml leaves 4-char ASCII names unpadded', () => {
|
||||
withApp(() => {
|
||||
const html = globalThis.PotatoMesh.renderShortHtml('0ac7', 'CLIENT');
|
||||
assert.ok(!html.includes(' 0ac7'), 'should not add leading space');
|
||||
assert.ok(!html.includes('0ac7 '), 'should not add trailing space');
|
||||
});
|
||||
});
|
||||
|
||||
test('renderShortHtml adds single space padding for short emoji names', () => {
|
||||
withApp(() => {
|
||||
const html = globalThis.PotatoMesh.renderShortHtml('\u26A1', 'CLIENT');
|
||||
// Should produce " ⚡ " — one leading, one trailing space (as )
|
||||
assert.ok(html.includes(' \u26A1 '), 'emoji should have one space on each side');
|
||||
// Should NOT have double leading spaces
|
||||
assert.ok(!html.includes(' \u26A1'), 'should not double-pad emoji');
|
||||
});
|
||||
});
|
||||
|
||||
test('renderShortHtml adds single space padding for surrogate pair emoji', () => {
|
||||
withApp(() => {
|
||||
const html = globalThis.PotatoMesh.renderShortHtml('\uD83D\uDE43', 'CLIENT');
|
||||
// 🙃 is a surrogate pair (length 2 in JS) but 1 grapheme
|
||||
assert.ok(html.includes(' \uD83D\uDE43 '), 'surrogate emoji should have one space on each side');
|
||||
});
|
||||
});
|
||||
|
||||
test('renderShortHtml adds single space padding for ZWJ emoji sequence', () => {
|
||||
withApp(() => {
|
||||
const zwj = '\u{1F3C3}\u{200D}\u{2642}\u{FE0F}'; // 🏃♂️ — length 5, 1 grapheme
|
||||
const html = globalThis.PotatoMesh.renderShortHtml(zwj, 'CLIENT');
|
||||
assert.ok(html.includes(` ${zwj} `), 'ZWJ emoji should have one space on each side');
|
||||
});
|
||||
});
|
||||
|
||||
test('renderShortHtml adds single space padding for plain 2-char name', () => {
|
||||
withApp(() => {
|
||||
const html = globalThis.PotatoMesh.renderShortHtml('ab', 'CLIENT');
|
||||
assert.ok(html.includes(' ab '), '2-char name should have one space on each side');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1853,22 +1853,22 @@ export function initializeApp(config) {
|
||||
infoAttr = attrParts.join('');
|
||||
}
|
||||
if (!short) {
|
||||
return `<span class="short-name" style="background:#ccc"${titleAttr}${infoAttr}>? </span>`;
|
||||
return `<span class="short-name" style="background:#ccc"${titleAttr}${infoAttr}> ? </span>`;
|
||||
}
|
||||
// Centre the label within a 4-column badge. padStart alone only adds
|
||||
// leading spaces, producing " C" for a 1-char name with no trailing
|
||||
// space. Instead distribute padding evenly: 1-char → " C ", 2-char →
|
||||
// " AB ", 3-char → " ABC", 4-char → unchanged. Names already at 4+
|
||||
// chars are left as-is (meshtastic always stores exactly 4; the Ruby
|
||||
// COMPANION override also produces exactly 4).
|
||||
// Pad the label for the badge. For plain-ASCII names that are already
|
||||
// 4 characters (meshtastic always stores exactly 4) no padding is added.
|
||||
// Shorter names or names containing emoji/non-ASCII get a single space
|
||||
// on each side — grapheme width varies too much for character-count
|
||||
// centering to work reliably.
|
||||
const raw = String(short);
|
||||
const graphemeCount = typeof Intl !== 'undefined' && Intl.Segmenter
|
||||
? [...new Intl.Segmenter().segment(raw)].length
|
||||
: raw.length;
|
||||
let centred;
|
||||
if (raw.length >= 4) {
|
||||
if (graphemeCount >= 4) {
|
||||
centred = raw;
|
||||
} else {
|
||||
const leading = Math.ceil((4 - raw.length) / 2);
|
||||
const trailing = 4 - raw.length - leading;
|
||||
centred = ' '.repeat(leading) + raw + ' '.repeat(trailing);
|
||||
centred = ` ${raw} `;
|
||||
}
|
||||
const padded = escapeHtml(centred).replace(/ /g, ' ');
|
||||
const protocol = nodeData?.protocol ?? null;
|
||||
|
||||
@@ -274,6 +274,15 @@ h1 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.site-title__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: inherit;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.site-title-text {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
|
||||
@@ -1311,6 +1311,13 @@ RSpec.describe "Potato Mesh Sinatra app" do
|
||||
expect(last_response.body).to include('class="footer-content"')
|
||||
end
|
||||
|
||||
it "renders the site title as a link to the dashboard" do
|
||||
get "/"
|
||||
|
||||
expect(last_response.body).to include('class="site-title__link"')
|
||||
expect(last_response.body).to match(%r{<a href="/" class="site-title__link">})
|
||||
end
|
||||
|
||||
it "renders the federation instance selector when federation is enabled" do
|
||||
get "/"
|
||||
|
||||
@@ -6980,6 +6987,14 @@ RSpec.describe "Potato Mesh Sinatra app" do
|
||||
expect(last_response.body).to include(node["node_id"])
|
||||
end
|
||||
|
||||
it "does not render the meta row on the node detail page" do
|
||||
node = nodes_fixture.first
|
||||
get "/nodes/#{node["node_id"]}"
|
||||
expect(last_response).to be_ok
|
||||
expect(last_response.body).not_to include('id="metaRow"')
|
||||
expect(last_response.body).not_to include('id="refreshBtn"')
|
||||
end
|
||||
|
||||
it "returns 404 when the node cannot be located" do
|
||||
get "/nodes/!deadbeef"
|
||||
expect(last_response.status).to eq(404)
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
show_footer = !full_screen_view || %i[charts federation].include?(view_mode)
|
||||
footer_slim = %i[charts federation].include?(view_mode)
|
||||
show_filter_input = !%i[node_detail charts federation].include?(view_mode)
|
||||
show_meta_row = !%i[charts federation].include?(view_mode)
|
||||
show_meta_row = !%i[node_detail charts federation].include?(view_mode)
|
||||
nodes_nav_href = "/nodes"
|
||||
nodes_nav_active = %i[nodes node_detail].include?(view_mode)
|
||||
federation_nav_enabled = !private_mode && federation_enabled
|
||||
@@ -99,8 +99,10 @@
|
||||
<header class="site-header">
|
||||
<div class="site-header__left">
|
||||
<h1 class="site-title">
|
||||
<img src="/potatomesh-logo.svg" alt="" aria-hidden="true" />
|
||||
<span class="site-title-text"><%= site_name %></span>
|
||||
<a href="/" class="site-title__link">
|
||||
<img src="/potatomesh-logo.svg" alt="" aria-hidden="true" />
|
||||
<span class="site-title-text"><%= site_name %></span>
|
||||
</a>
|
||||
</h1>
|
||||
<% if federation_nav_enabled %>
|
||||
<div class="header-federation">
|
||||
|
||||
Reference in New Issue
Block a user