It downloads results to a writable location (for ex. - attached USB drive) from either local (itself) or remote mikrotik.
HTML files are than rebased to allow local navigation. Feel free to test/discuss/etc.
Syntax styling will not be discussed
Code: Select all
# Backup graphing data
# Copyright 2023 by Dmitry F. https://t.me/NoOneNoOne
# GNU General Public License version 3
# inspired by:
# https://wiki.mikrotik.com/wiki/Backup_graphing_data
# https://forum.mikrotik.com/viewtopic.php?t=39166
# https://router-os.github.io/Backup-graphing-data.html
:local "graphAddress" "192.168.88.1";
:local "graphPort" "80";
:local "baseDST" "usb1-part1/.log/graphs_";
:local "indexHTML" "index.html";
:local "graphImages" {"daily.gif"; "weekly.gif" ; "monthly.gif" ; "yearly.gif"};
:local "rootFiles" {""; "favicon.png"; "favicon.svg"; "graph.css"};
# functions
:local "urlEncode" do={
:local "input" [:tostr $1];
:if ([:len $"input"]=0) do={ :return ""; }
:local "ret" "";
:local "chars" "\n\r !\"#\$%&'()*+,:;<=>\?@[\\]^`{|}~";
:local "subs" {
"%0A"; "%0D"; "%20"; "%21"; "%22"; "%23"; "%24"; "%25"; "%26"; "%27";
"%28"; "%29"; "%2A"; "%2B"; "%2C"; "%3A"; "%3B"; "%3C"; "%3D"; "%3E"; "%3F";
"%40"; "%5B"; "%5C"; "%5D"; "%5E"; "%60"; "%7B"; "%7C"; "%7D"; "%7E"
};
:for "i" from=0 to=([:len $"input"]-1) do={
:local "char" [:pick $"input" $"i"];
:local "replace" [:find $"chars" $"char"];
:if ($"replace">=0) do={
:set $"char" ($"subs"->$"replace");
}
:set $"ret" ($"ret".$"char");
}
:return $"ret";
}
:local "rebaseHTML" do={
:local "ok" (true);
:local "content" "";
:local "newContent" "";
:local "contentLen" 0;
:local "lineEnd" 0;
:local "line" "";
:local "lastEnd" 0;
:local "result" [:toarray ""];
:local "pos" 0;
:local "matched" (false);
:local "depth" "";
:local "resourcesURLs" [:toarray ""];
:if ([:tostr $"url"]="" or [:tostr $"dstFile"]="") do={ :return []; }
:if ([:tostr $"indexHTML"]="") do={ :set $"indexHTML" "index.html"; }
:for "i" from=1 to=(0+[:tonum $"level"]) step=1 do={
:set $"depth" ($"depth"."../");
}
:local "regExArr" {
# <p><a href="/graphs/">Main page</a>
{"re"=".*<a\\s+href=\"\\/graphs\\/\">.*";
"todo"={
{"find"="href=\"/graphs/"; "replace"=("href=\"".$"depth")};
{"find"="/\">"; "insert"=("/".$"indexHTML")}
}
};
{"re"=".*<a\\s+href='\\/graphs\\/'>.*";
"todo"={
{"find"="href='/graphs/"; "replace"=("href='".$"depth")};
{"find"="/'>"; "insert"=("/".$"indexHTML")}
}
};
{"re"=".*<a\\s+href=\\/graphs\\/>.*";
"todo"={
{"find"="href=/graphs/"; "replace"=("href=".$"depth")};
{"find"="/>"; "insert"=("/".$"indexHTML")}
}
};
# <li><a href='cpu/'>CPU usage</a>
{"re"=".*<a\\s+href=\".*\\/\">.*";
"todo"={
{"find"="href=\"/"; "replace"=("href=\"".$"depth")};
{"find"="/\">"; "insert"=("/".$"indexHTML")}
}
};
{"re"=".*<a\\s+href='.*\\/'>.*";
"todo"={
{"find"="href='/"; "replace"=("href='".$"depth")};
{"find"="/'>"; "insert"=("/".$"indexHTML")}
}
};
{"re"=".*<a\\s+href=.*\\/>.*";
"todo"={
{"find"="href=/"; "replace"=("href=".$"depth")};
{"find"="/>"; "insert"=("/".$"indexHTML")}
}
};
# <link rel=icon href=/favicon.png>
# <link rel=stylesheet href=/graph.css>
{"re"=".*<link\\s+.*\\s+href=\\/.*>.*";
"todo"={
{"find"="href=/"; "replace"=("href=".$"depth")}
}
};
{"re"=".*<link\\s+.*\\s+href='\\/.*'>.*";
"todo"={
{"find"="href='/"; "replace"=("href='".$"depth")}
}
}
}
:local "regExArrMainPage" {
# <li><a href='/graphs/iface/bridge/'>bridge</a>
{"re"="<li><a\\s+href=\"\\/graphs\\/(iface|queue)\\/.*\\/\">.*</a>";
"todo"={
{"find"="href='/"; "replace"=("href='".$"depth")};
{"find"="/\">"; "insert"=("/".$"indexHTML")};
{"find"="\">"; "margin"="<"; "types"={"/iface/";"/queue/"}}
}
};
{"re"="<li><a\\s+href='\\/graphs\\/(iface|queue)\\/.*\\/'>.*</a>";
"todo"={
{"find"="href='/"; "replace"=("href='".$"depth")};
{"find"="/'>"; "insert"=("/".$"indexHTML")};
{"find"="'>"; "margin"="<"; "types"={"/iface/";"/queue/"}}
}
};
{"re"="<li><a\\s+href=\\/graphs\\/(iface|queue)\\/.*\\/>.*</a>";
"todo"={
{"find"="href=/"; "replace"=("href=".$"depth")};
{"find"="/>"; "insert"=("/".$"indexHTML")};
{"find"="'>"; "margin"="<"; "types"={"/iface/";"/queue/"}}
}
}
}
:if ((0+[:tonum $"level"])=0) do={
:set $"regExArr" ($"regExArrMainPage", $"regExArr");
}
:local "fetchTime" [:time {
:do {
:set $"result" [/tool fetch url=$"url" mode=http output=user as-value];
} on-error={ :set $"ok" (false); }
}];
:put (" status: ". $"result"->"status");
:put (" total: ". $"result"->"downloaded"."KiB");
:put (" duration: ". $"fetchTime");
:put "";
:if ($"ok" and $"result"->"status"="finished") do={
:set $"content" ($"result"->"data");
:set $"contentLen" [:len $"content"];
:do {
:set $"lineEnd" [:find $"content" "\n" $"lastEnd"];
:set $"line" [:pick $"content" $"lastEnd" $"lineEnd"];
:set $"lastEnd" ($"lineEnd"+1);
# :put ($"line"." ---> ".($"line"~$"regEx"));
:set $"matched" (false);
:foreach "regEx" in=$"regExArr" do={
:if (!$"matched" and $"line"~($"regEx"->"re")) do={
:set $"matched" (true);
:foreach "todo" in=($"regEx"->"todo") do={
:set $"pos" [:find $"line" ($"todo"->"find")];
:if ($"pos">0) do={
:if ([:len ($"todo"->"insert")]>0) do={
:set $"line" ([:pick $"line" 0 $"pos"].($"todo"->"insert").[:pick $"line" ($"pos"+1) [:len $"line"]]);
}
:if ([:len ($"todo"->"replace")]>0) do={
:set $"line" ([:pick $"line" 0 $"pos"].($"todo"->"replace").[:pick $"line" ($"pos"+[:len ($"todo"->"find")]) [:len $"line"]]);
}
:if ([:len ($"todo"->"types")]>0) do={
:local "type" "";
:foreach "t" in=($"todo"->"types") do={
:if ($"type"="" and [:find $"line" $"t"]>0) do={ :set $"type" $"t"; }
}
:set $"resourcesURLs" ($"resourcesURLs", {{"type"=$"type"; "name"=([:pick $"line" ($"pos"+[:len ($"todo"->"find")]) [:find $"line" ($"todo"->"margin") ($"pos"+[:len ($"todo"->"find")])]])}});
}
}
}
}
}
# :put $"line";
:set $"newContent" ($"newContent".$"line"."\r");
} while ($"lastEnd"<$"contentLen");
:do {
/file {
:if ([:len [find name=$"dstFile"]]>0) do={
set $"dstFile" contents=$"newContent";
} else={
add name=$"dstFile" contents=$"newContent";
}
}
} on-error={ :set $"ok" (false); };
}
:if (!$"ok") do={
:set $"resourcesURLs" (false);
}
:return $"resourcesURLs";
}
# main script starts here
:local "baseURL" ("http://" . $"graphAddress".":".$"graphPort"."/");
:local "curDate" [/system clock get date];
:local "curYear" [:pick $"curDate" 0 4];
:local "curMonth" [:pick $"curDate" 5 7];
:local "curDay" [:pick $"curDate" 8 10];
:local "systemName" [/system identity get name];
:local "outFolder" ($"systemName"."_".$"curYear"."-".$"curMonth"."-".$"curDay"."/");
:local "graphPath";
:local "graphPathDST";
:local "resourcesURLs" [:toarray ""];
:local "ok" (true);
# Fetch main graphs page and build resources list based on main page URLs
:put "==> Fetching main page graphs... <==";
:set $"graphPathDST" ($"baseDST".$"outFolder");
/file remove [find name=$"graphPathDST"];
:foreach "file" in=$"rootFiles" do={
:if ($"ok") do={
:if ($"file"="") do={
:put (" ".$"baseURL"."graphs/");
:set $"resourcesURLs" [$"rebaseHTML" url=($"baseURL"."graphs/") dstFile=($"graphPathDST".$"indexHTML") level=0];
:set $"ok" ($"resourcesURLs"!=false);
} else={
:put (" ".$"baseURL".$"file");
:do {
/tool fetch url=($"baseURL".[$"urlEncode" $"file"]) mode=http dst-path=($"graphPathDST".$"file") output=file;
} on-error={ :set $"ok" (false); };
}
}
}
:if (!$"ok") do={
/file remove [find name=($"baseDST".$"outFolder")];
:error "Graphs download error occured.";
}
# main resources (cpu/ram/hdd) graphs
:put "==> Fetching resources graphs... <==";
:foreach "resource" in={"cpu/"; "ram/"; "hdd/"} do={
:if ($"ok") do={
:set $"graphPath" ("graphs/".$"resource");
:set $"graphPathDST" ($"baseDST".$"outFolder".$"resource");
:put (" ".$"baseURL".$"graphPath");
:set $"ok" ([$"rebaseHTML" url=($"baseURL".[$"urlEncode" $"graphPath"]) dstFile=($"graphPathDST".$"indexHTML") level=1]!=false);
:if ($"ok") do={
:foreach "image" in=$"graphImages" do={
:if ($"ok") do={
:put (" ".$"baseURL".$"graphPath".$"image");
:do {
/tool fetch mode=http url=($"baseURL".[$"urlEncode" ($"graphPath".$"image")]) dst-path=($"graphPathDST".$"image") output=file;
} on-error={ :set $"ok" (false); };
}
}
}
}
}
:if (!$"ok") do={
/file remove [find name=($"baseDST".$"outFolder")];
:error "Graphs download error occured.";
}
# other resources (interface/queue) graphs
:put "==> Fetching interfaces/queues graphs... <==";
:foreach "resource" in=$"resourcesURLs" do={
:if ($"ok") do={
:put (" resource (".$"resource"->"type"."): ".$"resource"->"name");
:set $"graphPath" ("graphs".$"resource"->"type".$"resource"->"name"."/")
:set $"graphPathDST" ($"baseDST".$"outFolder"."graphs".$"resource"->"type".$"resource"->"name"."/");
:put (" ".$"baseURL".$"graphPath");
:set $"ok" ([$"rebaseHTML" url=($"baseURL".[$"urlEncode" $"graphPath"]) dstFile=($"graphPathDST".$"indexHTML") level=3]!=false);
:if ($"ok") do={
:foreach "image" in=$"graphImages" do={
:put (" ".$"baseURL".$"graphPath".$"image");
:do {
/tool fetch mode=http url=($"baseURL".[$"urlEncode" ($"graphPath".$"image")]) dst-path=($"graphPathDST".$"image") output=file;
} on-error={ :set $"ok" (false); };
}
}
}
}
:if (!$"ok") do={
/file remove [find name=($"baseDST".$"outFolder")];
:error "Graphs download error occured.";
}
:put "==> Done. <==";