diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b0144..5e1e81f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ IMPORTANT NOTE: This version only works on CACTI 1.x++! * issue#322: Refresh the bundled manual with current installation, upgrade, editor, image management and troubleshooting guides; retain the configuration and data-source references and repair documentation links * issue#322: Update editor and map-management screenshots, including map creation, management, the viewer, hover graphs and Advanced settings; use compact previews with full-size links and refresh cached documentation assets * feature#322: Add themed Docs access from the map heading and a Docs button after Rebuild All in map management, opening the bundled manual in a new tab +* feature#321: Group default link width and appearance controls under Link Appearance Defaults in Map Style, and clarify Default Link Capacity in Map Properties +* feature#321: Add Edge and Centre comment styles to Map Style, with an individual override and Use map default option in Link Properties +* feature#321: Add IN/OUT traffic-label positions to Map Style and per-link Advanced settings, with 75%/25% defaults and an option to restore inheritance; keep traffic-label and comment positions separate --- 2.0 --- diff --git a/js/editor.js b/js/editor.js index eafdbb2..ee48c20 100644 --- a/js/editor.js +++ b/js/editor.js @@ -1312,6 +1312,11 @@ function prime_link_form(name) { $('#link_hover').val(wmDecodeEditorUrl(mylink.overliburl)); $('#viastyle').val(mylink.viastyle); + $('#link_commentstyle').val(mylink.commentstylechoice); + $('#link_labelposmode').val(mylink.labelposmode); + $('#link_labelposin').val(mylink.labelposin); + $('#link_labelposout').val(mylink.labelposout); + $('#link_labelposmode').trigger('change'); $('#link_commentin').val(mylink.commentin); $('#link_commentout').val(mylink.commentout); $('#link_commentposin').val(mylink.commentposin); @@ -1768,6 +1773,9 @@ function wmBuildLinkSections() { dlg.find(fields).closest('tr').appendTo(table); return panel; } + $('#link_labelposmode').on('change.wmPositions', function() { + $('#link_labelposin,#link_labelposout').prop('disabled', $(this).val() !== 'custom'); + }); var hovers = group('wm-link-hovers', wmEditorText.additionalGraphs, '#link_template,#link_picker'); dlg.find('.wm-link-section p').addClass('wm-link-group-hint').prependTo(hovers); dlg.find('.wm-link-section').remove(); @@ -1777,7 +1785,8 @@ function wmBuildLinkSections() { wmRenderLinkExtraGraphs(); $('#link_target_picker_rep').remove(); var advanced = group('wm-link-advanced', wmEditorText.advanced, - '#link_target,#link_infourl,#wm-link-primary-hover,#wm-link-extra-hover', wmEditorText.linkAdvancedHint); + '#link_labelposmode,#link_target,#link_infourl,#wm-link-primary-hover,#wm-link-extra-hover', wmEditorText.linkAdvancedHint); + $('#link_labelposmode').closest('tr').prependTo(advanced.find('table')); var sourceRow = $('
Bandwidth: Set the capacity used to calculate utilisation, for example 100M or 1G. K, M, G and T are supported. Select Same As "In" when both directions have equal capacity.
\n" +"Traffic interface: Search by device, port or description, or select Browse all. Choose an interface, then click Use interface to set the primary measured source, click destination and main hover graph. Additional measured sources and hover graphs are kept.
\n" +"Appearance and comments: Via Style controls curved or angled bends; Link Width controls arrow thickness. IN Comment and OUT Comment add text along each direction. The percentage sets the text position along the link. Comment Style places comments beside the link (Edge) or over the arrow (Centre); Use map default follows Map Style.
\n" +"Additional hover graphs: Show a maximum of two hover graphs: the main traffic graph and one extra, such as CPU, latency or another interface. Filter by graph type narrows the choices. Add hover graph adds the extra; Replace hover graphs changes it; Remove graph removes it. The main traffic graph, measured traffic and link click destination stay unchanged. In the viewer, each Cacti preview opens its own full graph when clicked.
\n" +"Advanced settings: Optional controls for moving the IN/OUT traffic labels, combining measured sources, changing the click destination and entering custom hover image URLs. Traffic label positions move the utilisation boxes along the link, independently of comment positions. Use map default follows Map Style. A normal traffic link usually needs no changes here.
\n" +"Combining traffic sources: Add traffic source adds another interface to the map total, separately for IN and OUT. Remove source removes an additional source. Change the primary source using Use interface at the top. The combined traffic is compared with the configured bandwidth to calculate utilisation. No combined Cacti graph is created automatically; create a matching aggregate graph in Cacti and enter its click and hover URLs if needed. Separate map links are usually clearer for primary and backup connections.
\n" +"Manual sources and URLs: The source text box edits the complete measured-source list, including the primary source. Click destination URL controls where clicking the map link goes. Main hover graph URL sets the first preview; Additional graph URL(s) accepts one extra image URL. This extra is also managed under Additional hover graphs.
\n" +"Tidy and Via: Tidy adjusts the link layout. Via lets you place a bend point on the map.
\n" +"Edit Raw Configuration: Edit the underlying map syntax for settings beyond this form.
" +msgstr "" + +#: weathermap-cacti-plugin-editor.php +msgid "Default Link Capacity" +msgstr "" + +#: weathermap-cacti-plugin-editor.php +msgid "Set the map’s title, legend, timestamp, background and canvas size, plus the default bandwidth for links. Map dimensions are measured in pixels; link appearance defaults are in Map Style." +msgstr "" + +#: weathermap-cacti-plugin-editor.php +msgid "Link Appearance Defaults" +msgstr "" + #: lib/viewer.controls.php weathermap-cacti-plugin-mgmt.php msgid "Docs" msgstr "" diff --git a/tests/Support/LinkLayoutControlsRegression.php b/tests/Support/LinkLayoutControlsRegression.php new file mode 100644 index 0000000..1caadba --- /dev/null +++ b/tests/Support/LinkLayoutControlsRegression.php @@ -0,0 +1,89 @@ +ReadConfig($file); + $request['config_revision'] = \WeathermapConfig\snapshotRevision($map->config_revision, $map->config_dependencies, $map->config_root_identity); + $GLOBALS['__test_nfilter_request'] = $request; + $action($file); + $saved = new WeatherMap(); + layout_assert($saved->ReadConfig($file), 'reload'); + return $saved; +} +$file = sys_get_temp_dir() . '/wm-layout-' . bin2hex(random_bytes(8)) . '.conf'; +$include = $file . '.included'; +try { + file_put_contents($file, "NODE A\n POSITION 10 10\nNODE B\n POSITION 100 100\nLINK DEFAULT\n WIDTH 7\nLINK main\n NODES A B\nLINK other\n NODES A B\n WIDTH 12\n"); + $style = ['mapstyle_htmlstyle'=>'overlib', 'mapstyle_legendfont'=>'4', 'mapstyle_keystyle'=>'classic', 'mapstyle_linklabels'=>'percent', 'mapstyle_arrowstyle'=>'compact', 'mapstyle_nodefont'=>'3', 'mapstyle_linkfont'=>'2', 'mapstyle_commentfont'=>'1', 'mapstyle_nodewidth'=>'0', 'mapstyle_nodeheight'=>'0', 'mapstyle_linkwidth'=>'0', 'mapstyle_linkheight'=>'0', 'mapstyle_defaultwidth'=>'9', 'mapstyle_commentstyle'=>'center', 'mapstyle_labelposin'=>'80', 'mapstyle_labelposout'=>'20']; + $saved = layout_submit($file, $style, 'setMapStyle'); + layout_assert($saved->links['main']->width == 9 && $saved->links['other']->width == 12, 'width moved to Map Style and preserves custom width'); + layout_assert($saved->links['main']->commentstyle === 'center' && $saved->links['main']->labeloffset_in == 80, 'style defaults applied'); + $link = ['link_name'=>'main', 'link_width'=>'9', 'link_infourl'=>'', 'link_hover'=>'', 'link_commentin'=>'In', 'link_commentout'=>'Out', 'link_commentposin'=>'95', 'link_commentposout'=>'5', 'link_target'=>'', 'link_bandwidth_in'=>'1G', 'link_bandwidth_out'=>'1G', 'viastyle'=>'curved', 'link_commentstyle'=>'edge', 'link_labelposmode'=>'custom', 'link_labelposin'=>'90', 'link_labelposout'=>'10']; + $saved = layout_submit($file, $link, 'setLinkProperties'); + layout_assert($saved->links['main']->commentstyle === 'edge' && $saved->links['main']->labeloffset_in == 90, 'per-link override applied'); + layout_assert($saved->links['other']->commentstyle === 'center' && $saved->links['other']->labeloffset_in == 80, 'other link unchanged'); + $link['link_commentstyle'] = 'default'; $link['link_labelposmode'] = 'default'; + $saved = layout_submit($file, $link, 'setLinkProperties'); + layout_assert($saved->links['main']->commentstyle === 'center' && $saved->links['main']->labeloffset_in == 80, 'default restored'); + $style['mapstyle_defaultwidth'] = '0'; $style['mapstyle_labelposin'] = '101'; + $saved = layout_submit($file, $style, 'setMapStyle'); + layout_assert($saved->links['DEFAULT']->width == 9 && $saved->links['DEFAULT']->labeloffset_in == 80, 'invalid layout values ignored'); + // Saving Map Properties must no longer reset the width now owned by Map Style. + $saved = layout_submit($file, ['map_title'=>'Example', 'map_legend'=>'Load', 'map_stamp'=>'', 'map_htmlfile'=>'', 'map_pngfile'=>'', 'map_width'=>'600', 'map_height'=>'300', 'map_bgfile'=>'--NONE--', 'map_linkdefaultbwin'=>'1G', 'map_linkdefaultbwout'=>'1G'], 'setMapProperties'); + layout_assert($saved->links['DEFAULT']->width == 9, 'Map Properties preserves width'); + // An override belongs to the link that chose it, never to its descendants. + file_put_contents($file, "NODE A\n POSITION 10 10\nNODE B\n POSITION 100 100\nLINK parent\n COMMENTSTYLE center\n BWLABELPOS 90 10\n SET editor_commentstyle_override 1\n SET editor_labelpos_override 1\nLINK child\n TEMPLATE parent\n NODES A B\nLINK fixed_child\n TEMPLATE parent\n NODES A B\n COMMENTSTYLE center\n BWLABELPOS 90 10\n SET editor_commentstyle_override 1\n SET editor_labelpos_override 1\n"); + $map = new WeatherMap(); layout_assert($map->ReadConfig($file), 'template marker fixture'); + layout_assert($map->links['child']->get_hint('editor_commentstyle_override') === null && $map->links['child']->get_hint('editor_labelpos_override') === null, 'template markers are not inherited'); + layout_assert(str_contains($map->links['child']->asJS(), 'commentstylechoice:' . js_escape('default')), 'inherited child is shown as default'); + layout_assert($map->WriteConfig($file), 'save explicit child markers matching template markers'); + $map = new WeatherMap(); layout_assert($map->ReadConfig($file), 'reload matching markers'); + layout_assert($map->links['fixed_child']->get_hint('editor_commentstyle_override') === '1' && $map->links['fixed_child']->get_hint('editor_labelpos_override') === '1', 'explicit matching markers survive serialization'); + wm_link_apply_overrides($map, $map->links['child'], 'edge', 'custom', '75', '25'); + layout_assert($map->WriteConfig($file), 'save child override'); + $map = new WeatherMap(); layout_assert($map->ReadConfig($file), 'reload child override'); + wm_link_apply_overrides($map, $map->links['child'], 'default', 'default', null, null); + layout_assert($map->WriteConfig($file), 'save reset'); + $map = new WeatherMap(); layout_assert($map->ReadConfig($file), 'reload reset'); + layout_assert($map->links['child']->get_hint('editor_commentstyle_override') === null && $map->links['child']->get_hint('editor_labelpos_override') === null, 'reset survives marked template reload'); + layout_assert($map->links['child']->commentstyle === 'center' && $map->links['child']->labeloffset_in == 90, 'reset inherits the named template values'); + + // Default edits follow local template chains, but cannot cross an unchanged INCLUDE. + $included = "LINK included\n COMMENTSTYLE edge\n BWLABELPOS 75 25\n WIDTH 7\n"; + file_put_contents($include, $included); + file_put_contents($file, "INCLUDE $include\nNODE A\n POSITION 10 10\nNODE B\n POSITION 100 100\nLINK DEFAULT\n COMMENTSTYLE edge\n BWLABELPOS 75 25\n WIDTH 7\nLINK external_child\n TEMPLATE included\n NODES A B\nLINK external_grandchild\n TEMPLATE external_child\n NODES A B\nLINK local_parent\n SET site_role local\nLINK local_child\n TEMPLATE local_parent\n NODES A B\nLINK local_grandchild\n TEMPLATE local_child\n NODES A B\nLINK fixed_parent\n SET editor_commentstyle_override 1\n SET editor_labelpos_override 1\nLINK fixed_descendant\n TEMPLATE fixed_parent\n NODES A B\n"); + $map = new WeatherMap(); layout_assert($map->ReadConfig($file), 'included template fixture'); + wm_comment_apply_style($map, null, null, null, 'center'); + wm_link_apply_default_positions($map, '85', '15'); + wm_comment_update_default($map, 'width', 9); + foreach (['included', 'external_child', 'external_grandchild'] as $name) { + layout_assert($map->links[$name]->commentstyle === 'edge' && $map->links[$name]->labeloffset_in == 75 && $map->links[$name]->width == 7, 'included branch unchanged: ' . $name); + } + foreach (['local_parent', 'local_child', 'local_grandchild'] as $name) { + layout_assert($map->links[$name]->commentstyle === 'center' && $map->links[$name]->labeloffset_in == 85 && $map->links[$name]->labeloffset_out == 15 && $map->links[$name]->width == 9, 'local template chain updated: ' . $name); + } + layout_assert($map->links['fixed_descendant']->commentstyle === 'edge' && $map->links['fixed_descendant']->labeloffset_in == 75, 'explicit parent stops propagation to descendants'); + layout_assert($map->WriteConfig($file), 'save inherited default edits'); + $map = new WeatherMap(); layout_assert($map->ReadConfig($file), 'reload included template edits'); + layout_assert(file_get_contents($include) === $included, 'included file is untouched'); + foreach (['external_child', 'external_grandchild'] as $name) { + layout_assert($map->links[$name]->commentstyle === 'edge' && $map->links[$name]->labeloffset_in == 75 && $map->links[$name]->width == 7, 'included branch stable after reload: ' . $name); + preg_match('/LINK ' . $name . '\n(.*?)(?=\nLINK |$)/s', file_get_contents($file), $block); + layout_assert(!preg_match('/COMMENTSTYLE|BWLABELPOS|WIDTH|editor_.*override/', $block[1]), 'no accidental serialized override: ' . $name); + } + + echo "Link layout controls regression PASS\n"; +} finally { + @unlink($file); + @unlink($include); +} diff --git a/tests/Unit/LinkLayoutControlsTest.php b/tests/Unit/LinkLayoutControlsTest.php new file mode 100644 index 0000000..bd8b048 --- /dev/null +++ b/tests/Unit/LinkLayoutControlsTest.php @@ -0,0 +1,46 @@ +WriteConfig($file); $saved = new WeatherMap(); $saved->ReadConfig($file); @@ -59,6 +59,54 @@ function cacti_count($values) { self::assertEquals(9, $font->size); self::assertEquals([0, 0, 255], $saved->links['DEFAULT']->commentfontcolour); self::assertStringContainsString('FONTDEFINE', file_get_contents($file)); + self::assertSame('center', $saved->links['DEFAULT']->commentstyle); + self::assertStringContainsString('COMMENTSTYLE center', file_get_contents($file)); + wm_comment_apply_style($saved, null, null, null, 'edge'); + self::assertTrue($saved->WriteConfig($file)); + $edge = new WeatherMap(); + self::assertTrue($edge->ReadConfig($file)); + self::assertSame('edge', $edge->links['DEFAULT']->commentstyle); + + // Explicit overrides equal to the default must survive save/reload and later default edits. + file_put_contents($file, "NODE A\n POSITION 10 10\nNODE B\n POSITION 100 100\nLINK DEFAULT\n COMMENTSTYLE edge\n BWLABELPOS 75 25\nLINK ordinary\n NODES A B\nLINK fixed\n NODES A B\nLINK rawcustom\n NODES A B\n COMMENTSTYLE center\n BWLABELPOS 90 10\nLINK named\n COMMENTSTYLE center\n BWLABELPOS 80 20\nLINK templated\n TEMPLATE named\n NODES A B\n"); + $layout = new WeatherMap(); + self::assertTrue($layout->ReadConfig($file)); + wm_link_apply_overrides($layout, $layout->links['fixed'], 'edge', 'custom', '75', '25'); + self::assertTrue($layout->WriteConfig($file)); + $layout = new WeatherMap(); + self::assertTrue($layout->ReadConfig($file)); + self::assertSame('1', $layout->links['fixed']->get_hint('editor_commentstyle_override')); + self::assertStringContainsString('commentstylechoice:' . js_escape('edge'), $layout->links['fixed']->asJS()); + self::assertStringContainsString('labelposmode:' . js_escape('custom'), $layout->links['fixed']->asJS()); + wm_comment_apply_style($layout, null, null, null, 'center'); + wm_link_apply_default_positions($layout, '85', '15'); + self::assertSame('edge', $layout->links['fixed']->commentstyle); + self::assertEquals(75, $layout->links['fixed']->labeloffset_in); + self::assertSame('center', $layout->links['ordinary']->commentstyle); + self::assertEquals(85, $layout->links['ordinary']->labeloffset_in); + self::assertEquals(90, $layout->links['rawcustom']->labeloffset_in); + self::assertEquals(80, $layout->links['templated']->labeloffset_in); + $before = serialize($layout->links['fixed']); + wm_link_apply_overrides($layout, $layout->links['fixed'], 'invalid', 'custom', '-1', '101'); + self::assertSame($before, serialize($layout->links['fixed'])); + wm_link_apply_default_positions($layout, '10.5', '25'); + self::assertEquals(85, $layout->links['DEFAULT']->labeloffset_in); + wm_link_apply_overrides($layout, $layout->links['fixed'], 'default', 'default', null, null); + self::assertSame('center', $layout->links['fixed']->commentstyle); + self::assertEquals(85, $layout->links['fixed']->labeloffset_in); + self::assertNull($layout->links['fixed']->get_hint('editor_commentstyle_override')); + self::assertTrue($layout->WriteConfig($file)); + $restored = new WeatherMap(); + self::assertTrue($restored->ReadConfig($file)); + wm_comment_apply_style($restored, null, null, null, 'edge'); + wm_link_apply_default_positions($restored, '70', '30'); + self::assertSame('edge', $restored->links['fixed']->commentstyle); + self::assertEquals(70, $restored->links['fixed']->labeloffset_in); + $html = wm_link_override_fields() . wm_link_position_fields($restored); + self::assertStringContainsString('Use map default', $html); + self::assertStringContainsString('id="mapstyle_labelposin"', $html); + self::assertStringContainsString('id="link_commentstyle"', $html); + } finally { if (file_exists($file)) { unlink($file); diff --git a/tests/Unit/MapStyleDefaultsTest.php b/tests/Unit/MapStyleDefaultsTest.php index 2359177..9b27d37 100644 --- a/tests/Unit/MapStyleDefaultsTest.php +++ b/tests/Unit/MapStyleDefaultsTest.php @@ -39,9 +39,9 @@ function wm_style_test_map() { $builtin = new WMFont(); $builtin->type = 'GD builtin'; $builtin->gdnumber = 2; - $default = (object)['defined_in'=>'/fixture/map.conf', 'bwfont'=> 100, 'commentfont'=> 100, 'commentfontcolour'=>['0', '0', '255']]; + $default = (object)['defined_in'=>'/fixture/map.conf', 'bwfont'=> 100, 'commentfont'=> 100, 'commentfontcolour'=>['0', '0', '255'], 'commentstyle'=>'edge']; - return (object)['configfile'=>'/fixture/map.conf', 'fonts'=>[2=>$builtin, 100=>$font], 'links'=>[':: DEFAULT ::'=>clone $default, 'DEFAULT'=>clone $default, 'ordinary'=>clone $default, 'custom'=>(object)['defined_in'=>'/fixture/map.conf', 'bwfont'=>2, 'commentfont'=>2, 'commentfontcolour'=>[255, 0, 0]]]]; + return (object)['configfile'=>'/fixture/map.conf', 'fonts'=>[2=>$builtin, 100=>$font], 'links'=>[':: DEFAULT ::'=>clone $default, 'DEFAULT'=>clone $default, 'ordinary'=>clone $default, 'custom'=>(object)['defined_in'=>'/fixture/map.conf', 'bwfont'=>2, 'commentfont'=>2, 'commentfontcolour'=>[255, 0, 0], 'commentstyle'=>'center']]]; } it('UpdatesMatchingDefaultsAndPreservesDifferentStyles', function () { $map = wm_style_test_map(); @@ -121,7 +121,7 @@ function wm_style_test_map() { $document = new DOMDocument(); $document->loadHTML('Traffic interface: Search by device, port or description, or select Browse all. Choose an interface, then click Use interface to set the primary measured source, click destination and main hover graph. Additional measured sources and hover graphs are kept.
-Appearance and comments: Via Style controls curved or angled bends; Link Width controls arrow thickness. IN Comment and OUT Comment add text along each direction. The percentage sets the text position along the link.
+Appearance and comments: Via Style controls curved or angled bends; Link Width controls arrow thickness. IN Comment and OUT Comment add text along each direction. The percentage sets the text position along the link. Comment Style places comments beside the link (Edge) or over the arrow (Centre); Use map default follows Map Style.
Additional hover graphs: Show a maximum of two hover graphs: the main traffic graph and one extra, such as CPU, latency or another interface. Filter by graph type narrows the choices. Add hover graph adds the extra; Replace hover graphs changes it; Remove graph removes it. The main traffic graph, measured traffic and link click destination stay unchanged. In the viewer, each Cacti preview opens its own full graph when clicked.
-Advanced settings: Optional controls for combining measured sources, changing the click destination and entering custom hover image URLs. A normal traffic link usually needs no changes here.
+Advanced settings: Optional controls for moving the IN/OUT traffic labels, combining measured sources, changing the click destination and entering custom hover image URLs. Traffic label positions move the utilisation boxes along the link, independently of comment positions. Use map default follows Map Style. A normal traffic link usually needs no changes here.
Combining traffic sources: Add traffic source adds another interface to the map total, separately for IN and OUT. Remove source removes an additional source. Change the primary source using Use interface at the top. The combined traffic is compared with the configured bandwidth to calculate utilisation. No combined Cacti graph is created automatically; create a matching aggregate graph in Cacti and enter its click and hover URLs if needed. Separate map links are usually clearer for primary and backup connections.
Manual sources and URLs: The source text box edits the complete measured-source list, including the primary source. Click destination URL controls where clicking the map link goes. Main hover graph URL sets the first preview; Additional graph URL(s) accepts one extra image URL. This extra is also managed under Additional hover graphs.
Tidy and Via: Tidy adjusts the link layout. Via lets you place a bend point on the map.
@@ -745,11 +746,8 @@