implement tag descriptions
authorDenis Ovsienko <denis@ovsienko.info>
Thu, 5 Jul 2018 10:57:59 +0000 (11:57 +0100)
committerDenis Ovsienko <denis@ovsienko.info>
Thu, 5 Jul 2018 10:57:59 +0000 (11:57 +0100)
In this first implementation the tag descriptions editor resides on a tab
separate from the main tag "tree" (in fact, forest) editor. This allows
to fit the form into smaller horizontal space and to authorize the
descriptions editor in a separate context (as the descriptions do not
affect RackCode processing, for example).

* install.php: add a new "description" column to TagTree
* upgrade.php: idem
* listCells(): expedite the new "description" column
* spotEntity(): idem
* getTagList(): idem
* getObjectiveTagTree(): idem
* buildTagCheckboxRows(): idem
* renderTagRowForViewer(): display the new column in the table
* renderTagTree(): add a table header
* renderTagRowForDescriptions(): a new function for tagtree-descriptions
* renderTagDescriptionsEditor(): idem
* serializeTags(): set CLASS and TITLE depending on the tag description
* printTagCheckboxTable(): idem
* navigation.php: register a new tab for the "tagtree" page
* ophandlers.php: add an $opspec_list entry for the new tab
* pi.css: specify the style for described tags

ChangeLog
wwwroot/css/pi.css
wwwroot/inc/database.php
wwwroot/inc/functions.php
wwwroot/inc/install.php
wwwroot/inc/interface-config.php
wwwroot/inc/interface-lib.php
wwwroot/inc/interface.php
wwwroot/inc/navigation.php
wwwroot/inc/ophandlers.php
wwwroot/inc/upgrade.php

index 6e13ded..fb11ee3 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -4,6 +4,7 @@
        update: ability to extend supported device breed list via plug-in
        update: add a server configuration report (same as before install/upgrade)
        new feature: "shared router" allocation (GH#210 by Vladimir Ivanov)
+       new feature: optional tag descriptions
 0.21.1 2017-10-22
        update: improve Percona server support (Mantis#1793)
        bugfix: fix an upgrade bug introduced in 0.21.0 (found by Chris James)
index 7ddafa4..4c0cce0 100644 (file)
@@ -166,6 +166,13 @@ td.tagbox.selected-inverted {
 td.tagbox.inverted {
        text-decoration: line-through;
 }
+a.tag-descr {
+       border-bottom: 1px dashed;
+}
+span.tag-descr {
+       border-bottom: 1px dashed;
+       cursor: help;
+}
 
 td.sticker {
        text-align: left;
index 3cd34cf..455b64f 100644 (file)
@@ -496,6 +496,7 @@ function listCells ($realm, $parent_id = 0)
                                'tag' => $taglist[$tag_id]['tag'],
                                'parent_id' => $taglist[$tag_id]['parent_id'],
                                'color' => $taglist[$tag_id]['color'],
+                               'description' => $taglist[$tag_id]['description'],
                                'user' => $row['tag_user'],
                                'time' => $row['tag_time'],
                        );
@@ -609,6 +610,7 @@ function spotEntity ($realm, $id, $ignore_cache = FALSE)
                                        'tag' => $taglist[$row['tag_id']]['tag'],
                                        'parent_id' => $taglist[$row['tag_id']]['parent_id'],
                                        'color' => $taglist[$row['tag_id']]['color'],
+                                       'description' => $taglist[$row['tag_id']]['description'],
                                        'user' => $row['tag_user'],
                                        'time' => $row['tag_time'],
                                );
@@ -620,6 +622,7 @@ function spotEntity ($realm, $id, $ignore_cache = FALSE)
                                'tag' => $taglist[$row['tag_id']]['tag'],
                                'parent_id' => $taglist[$row['tag_id']]['parent_id'],
                                'color' => $taglist[$row['tag_id']]['color'],
+                               'description' => $taglist[$row['tag_id']]['description'],
                                'user' => $row['tag_user'],
                                'time' => $row['tag_time'],
                        );
@@ -4271,7 +4274,7 @@ function getTagList ($extra_sql = '')
 {
        $result = usePreparedSelectBlade
        (
-               'SELECT id, parent_id, is_assignable, tag, LPAD(HEX(color), 6, "0") AS color ' .
+               'SELECT id, parent_id, is_assignable, tag, LPAD(HEX(color), 6, "0") AS color, description ' .
                "FROM TagTree ORDER BY tag ${extra_sql}"
        );
        return reindexById ($result->fetchAll (PDO::FETCH_ASSOC));
index 4b8a1b3..c7f97bf 100644 (file)
@@ -1892,6 +1892,7 @@ function getObjectiveTagTree ($tree, $realm, $preselect)
                                'parent_id' => $taginfo['parent_id'],
                                'refcnt' => $taginfo['refcnt'],
                                'color' => $taginfo['color'],
+                               'description' => $taginfo['description'],
                                'kids' => $subsearch
                        );
                else
index 2fd2286..4ff2a89 100644 (file)
@@ -1059,6 +1059,7 @@ function get_pseudo_file ($name)
   `is_assignable` enum('yes','no') NOT NULL DEFAULT 'yes',
   `tag` char(255) default NULL,
   `color` mediumint(8) unsigned DEFAULT NULL,
+  `description` char(255) COLLATE utf8_unicode_ci DEFAULT NULL,
   PRIMARY KEY  (`id`),
   UNIQUE KEY `tag` (`tag`),
   KEY `TagTree-K-parent_id` (`parent_id`),
index b00b39b..3d9cbd2 100644 (file)
@@ -892,7 +892,9 @@ function renderTagRowForViewer ($taginfo, $level = 0)
        if (count ($taginfo['kids']))
                printImageHREF ('node-expanded-static');
        echo '<span title="' . serializeTagStats ($taginfo) . '" class="' . getTagClassName ($taginfo['id']) . '">' . $taginfo['tag'];
-       echo '</span>' . ($refc ? " <i>(${refc})</i>" : '') . "</td></tr>\n";
+       echo '</span>' . ($refc ? " <i>(${refc})</i>" : '') . '</td>';
+       echo '<td>' . stringForTD ($taginfo['description'], 64) . '</td>';
+       echo "</tr>\n";
        foreach ($taginfo['kids'] as $kid)
                $self ($kid, $level + 1);
 }
@@ -900,6 +902,7 @@ function renderTagRowForViewer ($taginfo, $level = 0)
 function renderTagTree ()
 {
        echo '<center><table class=tagtree>';
+       echo '<tr><th>name</th><th>description</th></tr>';
        foreach (getTagTree() as $taginfo)
                renderTagRowForViewer ($taginfo);
        echo '</table></center>';
@@ -1037,6 +1040,56 @@ function renderTagTreeEditor ()
        echo '</table>';
 }
 
+function renderTagRowForDescriptions ($taginfo, $level = 0)
+{
+       $self = __FUNCTION__;
+
+       $trclass = $taginfo['is_assignable'] == 'yes' ? '' : ($taginfo['kidc'] ? ' trnull' : ' trwarning');
+       if (!count ($taginfo['kids']))
+               $level++; // Shift instead of placing a spacer. This won't impact any nested nodes.
+       $refc = $taginfo['refcnt']['total'];
+       echo "<tr class='${trclass}'>";
+
+       echo '<td align=left style="padding-left: ' . ($level * 16) . 'px;">';
+       printOpFormIntro ('updTagDescr', array ('id' => $taginfo['id']));
+       if (count ($taginfo['kids']))
+               printImageHREF ('node-expanded-static');
+       echo '<span title="' . serializeTagStats ($taginfo) . '" class="' . getTagClassName ($taginfo['id']) . '">' . $taginfo['tag'];
+       echo '</span>' . ($refc ? " <i>(${refc})</i>" : '') . "</td>";
+
+       echo '<td>';
+       if ($taginfo['description'] === NULL)
+               echo '&nbsp;';
+       else
+               echo getOpLink
+               (
+                       array ('op' => 'updTagDescr', 'id' => $taginfo['id'], 'description' => ''),
+                       '',
+                       'clear',
+                       'Clear value'
+               );
+       echo '</td>';
+
+       echo '<td><input type=text size=64 name=description value="';
+       echo stringForTextInputValue ($taginfo['description'], 0) . '"></td>';
+
+       echo '<td>' . getImageHREF ('save', 'Save changes', TRUE) . '</td>';
+       echo '</form>';
+       echo "</tr>\n";
+
+       foreach ($taginfo['kids'] as $kid)
+               $self ($kid, $level + 1);
+}
+
+function renderTagDescriptionsEditor()
+{
+       echo '<br><table cellspacing=0 cellpadding=5 align=center class=widetable>';
+       echo '<tr><th>tag name</th><th>&nbsp;</th><th>tag description</th></tr>';
+       foreach (getTagTree() as $taginfo)
+               renderTagRowForDescriptions ($taginfo);
+       echo '</table></center><br>';
+}
+
 function renderGraphCycleResolver()
 {
        global $pageno;
index 9e6d8b9..f76fcde 100644 (file)
@@ -786,12 +786,20 @@ function serializeTags ($chain, $baseurl = '')
                                $title .= "\n";
                        $title .= implode (" &rarr;  ", $parent_info);
                }
+               $has_descr = array_key_exists ('description', $taginfo) && $taginfo['description'] !== NULL;
+               if ($has_descr)
+                       $title .= "\n\n" . stringForOption ($taginfo['description'], 0);
                if ($title != '')
                        $title = "title='$title'";
 
-               $class = '';
-               if (isset ($taginfo['id']))
-                       $class = 'class="' . getTagClassName ($taginfo['id']) . '"';
+               if (! array_key_exists ('id', $taginfo))
+                       $class = '';
+               else
+               {
+                       $class = $has_descr ? 'tag-descr ' : '';
+                       $class .= getTagClassName ($taginfo['id']);
+                       $class = "class='${class}'";
+               }
 
                $href = '';
                if ($baseurl == '')
index cc0744a..9d26fca 100644 (file)
@@ -4406,6 +4406,8 @@ function buildTagCheckboxRows ($inputname, $preselect, $neg_preselect, $taginfo,
                $ret['input_extraattrs'] = 'disabled';
                $ret['tr_class'] .= (array_key_exists ('kidc', $taginfo) && $taginfo['kidc'] == 0) ? ' trwarning' : ' trnull';
        }
+       if (array_key_exists ('description', $taginfo) && $taginfo['description'] != '')
+               $ret['description'] = $taginfo['description'];
 
        if ($refcnt_realm != '' && isset ($taginfo['refcnt'][$refcnt_realm]))
                $ret['text_refcnt'] = $taginfo['refcnt'][$refcnt_realm];
@@ -4427,7 +4429,14 @@ function printTagCheckboxTable ($input_name, $preselect, $neg_preselect, $taglis
                        echo "<label><input type=checkbox class='${row['input_class']}' name='${row['input_name']}[]' value='${row['input_value']}'";
                        if (array_key_exists ('input_extraattrs', $row))
                                echo ' ' . $row['input_extraattrs'];
-                       echo '> <span class="' . $tag_class . '">' . $row['text_tagname'] . '</span>';
+                       if (! array_key_exists ('description', $row))
+                               $tag_extraattrs = '';
+                       else
+                       {
+                               $tag_extraattrs = 'title="' . stringForOption ($row['description'], 0) . '"';
+                               $tag_class .= ' tag-descr';
+                       }
+                       echo "> <span class='${tag_class}' ${tag_extraattrs}>${row['text_tagname']}</span>";
                        if (array_key_exists ('text_refcnt', $row))
                                echo " <i>(${row['text_refcnt']})</i>";
                        echo '</label></td></tr>';
index 45c3a51..0e58903 100644 (file)
@@ -643,14 +643,17 @@ $interface_requires['ui-*'] = 'interface-config.php';
 $page['tagtree']['title'] = 'Tag tree';
 $page['tagtree']['parent'] = 'config';
 $tab['tagtree']['default'] = 'View';
-$tab['tagtree']['edit'] = 'Edit';
+$tab['tagtree']['edit'] = 'Edit tree';
+$tab['tagtree']['descriptions'] = 'Edit descriptions';
 $tab['tagtree']['resolve'] = 'Circular references';
 $tabhandler['tagtree']['default'] = 'renderTagTree';
 $tabhandler['tagtree']['edit'] = 'renderTagTreeEditor';
+$tabhandler['tagtree']['descriptions'] = 'renderTagDescriptionsEditor';
 $tabhandler['tagtree']['resolve'] = 'renderGraphCycleResolver';
 $ophandler['tagtree']['edit']['createTag'] = 'tableHandler';
 $ophandler['tagtree']['edit']['destroyTag'] = 'tableHandler';
 $ophandler['tagtree']['edit']['updateTag'] = 'updateTag';
+$ophandler['tagtree']['descriptions']['updTagDescr'] = 'tableHandler';
 $ophandler['tagtree']['resolve']['updateTag'] = 'updateTag';
 $trigger['tagtree']['resolve'] = 'triggerGraphCycleResolver';
 $interface_requires['tagtree-*'] = 'interface-config.php';
index 3565794..9e86871 100644 (file)
@@ -359,6 +359,19 @@ $opspec_list['tagtree-edit-destroyTag'] = array
                array ('url_argname' => 'tag_id', 'table_colname' => 'id', 'assertion' => 'natural'),
        ),
 );
+$opspec_list['tagtree-descriptions-updTagDescr'] = array
+(
+       'table' => 'TagTree',
+       'action' => 'UPDATE',
+       'set_arglist' => array
+       (
+               array ('url_argname' => 'description', 'assertion' => 'string0', 'translator' => 'nullIfEmptyStr'),
+       ),
+       'where_arglist' => array
+       (
+               array ('url_argname' => 'id', 'assertion' => 'natural'),
+       ),
+);
 $opspec_list['8021q-vstlist-add'] = array
 (
        'table' => 'VLANSwitchTemplate',
index 1f88806..eef455b 100644 (file)
@@ -1312,6 +1312,7 @@ INSERT INTO `Config` (varname, varvalue, vartype, emptyok, is_hidden, is_userdef
                        $query[] = "ALTER TABLE IPv6Allocation MODIFY type ENUM('regular','shared','virtual','router','point2point','sharedrouter') NOT NULL DEFAULT 'regular'";
                        $query[] = "INSERT INTO Chapter (`id`, `sticky`, `name`) VALUES (39,'no','UPS models')";
                        $query[] = "INSERT INTO AttributeMap (`objtype_id`,`attr_id`,`chapter_id`) VALUES (12,2,39)"; // UPS (UPS models) -> HW type
+                       $query[] = "ALTER TABLE TagTree ADD COLUMN description char(255) DEFAULT NULL AFTER color";
                        // insert new queries here ^^^
                        $query[] = "UPDATE Config SET varvalue = '0.21.2' WHERE varname = 'DB_VERSION'";
                        break;