update_wiki_eye.pl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. #!/usr/bin/perl
  2. #
  3. # Синхронизация метаданных устройств в Wiki через API
  4. #
  5. # Запуск: perl /path/to/update_wiki_eye.pl
  6. #
  7. use utf8;
  8. use warnings;
  9. use Encode;
  10. use open qw(:std :encoding(UTF-8));
  11. no warnings 'utf8';
  12. use strict;
  13. use English;
  14. use lib "/opt/Eye/scripts";
  15. use FindBin '$Bin';
  16. use eyelib::config;
  17. use Data::Dumper;
  18. use File::Find;
  19. use File::Basename;
  20. use Fcntl qw(:flock);
  21. use LWP::UserAgent;
  22. use HTTP::Request;
  23. use JSON::XS;
  24. use URI::Escape;
  25. use Encode qw(decode_utf8 encode_utf8);
  26. # Блокировка для предотвращения параллельных запусков
  27. open(SELF, "<", $0) or die "Cannot open $0 - $!";
  28. flock(SELF, LOCK_EX | LOCK_NB) or exit 1;
  29. # Настройка API - аутентификация через параметры
  30. my $api_base = $config_ref{api_base} || 'http://localhost/api.php';
  31. my $api_key = $config_ref{api_key} || die "Ошибка: не настроен параметр api_key в конфигурации\n";
  32. my $api_login = $config_ref{api_login} || die "Ошибка: не настроен параметр api_login в конфигурации\n";
  33. # Базовые параметры аутентификации для всех запросов
  34. my $auth_params = "api_key=" . uri_escape($api_key) . "&api_login=" . uri_escape($api_login);
  35. # Инициализация HTTP-клиента
  36. my $ua = LWP::UserAgent->new(
  37. timeout => 30,
  38. agent => 'EyeWikiSync/1.0'
  39. );
  40. my $api_request_url = $api_base."?".$auth_params;
  41. my $option_filter = encode_json({ option_id => '61' });
  42. # === Получение пути к вики из таблицы config (id=61) ===
  43. my $api_request = $api_request_url."&get=table_record&table=config&filter=".uri_escape($option_filter);
  44. my $config_response = api_call($ua, 'GET', $api_request);
  45. if ($config_response->{error}) {
  46. die "Ошибка: не удалось получить путь к вики из таблицы config (id=61): " . $config_response->{error} . "\n";
  47. }
  48. my $wiki_path = $config_response->{data}->{value};
  49. if (!$wiki_path || !-d $wiki_path) {
  50. die "Ошибка: путь к вики не настроен или директория не существует: $wiki_path\n";
  51. }
  52. print "Путь к вики: $wiki_path\n\n";
  53. # Поиск подходящих файлов
  54. my %content;
  55. find(\&wanted, $wiki_path);
  56. my $processed = 0;
  57. my $errors = 0;
  58. foreach my $fname (sort keys %content) {
  59. print "Analyze $content{$fname}...";
  60. # Чтение файла
  61. open(my $fh, '<:encoding(UTF-8)', $content{$fname}) or do {
  62. warn "Не удалось открыть файл $content{$fname}: $!\n";
  63. $errors++;
  64. next;
  65. };
  66. my @lines = <$fh>;
  67. close($fh);
  68. chomp(@lines);
  69. # Извлечение IP из метаданных
  70. my $ip;
  71. foreach my $line (@lines) {
  72. if ($line =~ /\%META\:FIELD\{name="DeviceIP"/) {
  73. if ($line =~ /value="([0-9]{1,3}(?:\.[0-9]{1,3}){3})"/) {
  74. $ip = $1;
  75. last;
  76. }
  77. }
  78. }
  79. print "IP not found!\n" and next unless $ip && is_valid_ipv4($ip);
  80. print "$ip \n";
  81. # Получение записи из user_auth по IP через API
  82. my $auth_response = api_call($ua, 'GET', $api_request_url. "&get=user_auth&ip=" . uri_escape($ip));
  83. if ($auth_response->{error} || !$auth_response->{data} || !$auth_response->{data}->{wikiname}) {
  84. print "Record for ip: $ip not found or WikiName is empty! Err: $auth_response->{error}\n";
  85. next;
  86. }
  87. my $auth = $auth_response->{data};
  88. # Пропускаем шлюзы
  89. next if ($auth->{wikiname} =~ /^Gateway/);
  90. print "Found: $auth->{ip} $auth->{mac} ";
  91. my ($device_name, $device_port);
  92. my $error_msg;
  93. # Определение типа устройства и получение родительских данных
  94. eval {
  95. if ($auth->{wikiname} =~ /^(Switch|Router)/) {
  96. # Для коммутаторов/маршрутизаторов: получаем данные через цепочку устройств
  97. my $auth_list_response = api_call($ua, 'GET', $api_request_url. "&get=table_list&table=user_auth&filter=" . uri_escape(encode_json({ user_id => $auth->{user_id} })));
  98. die "empty user ip list" unless $auth_list_response->{data} && ref($auth_list_response->{data}) eq 'ARRAY' && @{$auth_list_response->{data}} > 0;
  99. my $IPS=();
  100. my $device;
  101. foreach my $auth_row (@{$auth_list_response->{data}}) {
  102. my $dev_ip = $auth_row->{ip};
  103. my $device_response = api_call($ua, 'GET', $api_request_url. "&get=table_record&table=devices&filter=" . uri_escape(encode_json({ ip => $dev_ip })));
  104. next if (exists $device_response->{error} || !$device_response->{data});
  105. $device = $device_response->{data};
  106. last;
  107. }
  108. die "this device not found!" unless $device;
  109. # Получаем аплинк-порт
  110. my $uplink_ports_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=device_ports&filter=" . uri_escape(encode_json({ device_id => $device->{id}, uplink => 1 })));
  111. die "Unknown connection" unless $uplink_ports_response->{data};
  112. my $parent_connect = $uplink_ports_response->{data};
  113. # Получаем целевой порт
  114. my $parent_port_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=device_ports&id=" . $parent_connect->{id});
  115. die "Unknown port connection" unless $parent_port_response->{data};
  116. my $parent_port = $parent_port_response->{data};
  117. # Получаем родительское устройство
  118. my $device_parent_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=devices&id=" . $parent_port->{device_id});
  119. die "Unknown parent device" unless $device_parent_response->{data};
  120. my $device_parent = $device_parent_response->{data};
  121. # Получаем авторизацию родительского устройства
  122. my $auth_parent_response = api_call($ua, 'GET', $api_request_url . "&get=user_auth&ip=" . uri_escape($device_parent->{ip}));
  123. die "Unknown auth for device" unless $auth_parent_response->{data} && $auth_parent_response->{data}->{wikiname};
  124. my $auth_parent = $auth_parent_response->{data};
  125. $device_name = $auth_parent->{wikiname};
  126. $device_port = $parent_port->{port};
  127. }
  128. else {
  129. # Для других устройств: получаем данные через соединения
  130. my $conn_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=connections&filter=" . uri_escape(encode_json({ auth_id => $auth->{id} })));
  131. die "Unknown connection" unless $conn_response->{data};
  132. my $conn = $conn_response->{data};
  133. # Получаем порт устройства
  134. my $device_port_rec_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=device_ports&id=" . $conn->{port_id});
  135. die "Unknown device port" unless $device_port_rec_response->{data};
  136. my $device_port_rec = $device_port_rec_response->{data};
  137. # Получаем устройство
  138. my $device_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=devices&id=" . $device_port_rec->{device_id});
  139. die "Unknown device" unless $device_response->{data} && $device_response->{data}->{user_id};
  140. my $device = $device_response->{data};
  141. # Получаем авторизацию устройства по user_id и IP
  142. my $device_auth_list_response = api_call($ua, 'GET', $api_request_url . "&get=table_record&table=user_auth&filter=" . uri_escape(encode_json({ user_id => $device->{user_id}, ip => $device->{ip} })));
  143. die "Unknown device auth" unless $device_auth_list_response->{data};
  144. my $device_auth = $device_auth_list_response->{data};
  145. die "Device auth has no wikiname" unless $device_auth->{wikiname};
  146. $device_name = $device_auth->{wikiname};
  147. $device_port = $device_port_rec->{port};
  148. }
  149. };
  150. if ($@) {
  151. $error_msg = $@;
  152. chomp($error_msg);
  153. print "Error: $error_msg\n";
  154. next;
  155. }
  156. # Подготовка обновленного содержимого файла
  157. my @wiki_dev;
  158. my %empty_fields = (parent => 1, parent_port => 1, mac => 1);
  159. foreach my $line (@lines) {
  160. if ($line =~ /\%META\:FIELD\{name="Parent"/) {
  161. $empty_fields{parent} = 0;
  162. if ($device_name) {
  163. push(@wiki_dev, '%META:FIELD{name="Parent" title="Parent" value="' . $device_name . '"}%');
  164. next;
  165. }
  166. }
  167. elsif ($line =~ /\%META\:FIELD\{name="ParentPort"/) {
  168. $empty_fields{parent_port} = 0;
  169. if ($device_port) {
  170. push(@wiki_dev, '%META:FIELD{name="ParentPort" title="Parent Port" value="' . $device_port . '"}%');
  171. next;
  172. }
  173. }
  174. elsif ($line =~ /\%META\:FIELD\{name="Mac"/) {
  175. $empty_fields{mac} = 0;
  176. if ($auth->{mac}) {
  177. push(@wiki_dev, '%META:FIELD{name="Mac" title="Mac" value="' . $auth->{mac} . '"}%');
  178. next;
  179. }
  180. }
  181. push(@wiki_dev, $line);
  182. }
  183. # Добавление отсутствующих полей
  184. if ($empty_fields{parent} && $device_name) {
  185. push(@wiki_dev, '%META:FIELD{name="Parent" title="Parent" value="' . $device_name . '"}%');
  186. }
  187. if ($empty_fields{parent_port} && $device_port) {
  188. push(@wiki_dev, '%META:FIELD{name="ParentPort" title="Parent Port" value="' . $device_port . '"}%');
  189. }
  190. if ($empty_fields{mac} && $auth->{mac}) {
  191. push(@wiki_dev, '%META:FIELD{name="Mac" title="Mac" value="' . $auth->{mac} . '"}%');
  192. }
  193. $device_name ||= 'None';
  194. $device_port ||= 'None';
  195. print "at $device_name $device_port\n";
  196. # Запись обновленного файла
  197. open(my $out_fh, '>:encoding(UTF-8)', $content{$fname}) or do {
  198. warn "Ошибка записи файла $content{$fname}: $!\n";
  199. $errors++;
  200. next;
  201. };
  202. print $out_fh $_ . "\n" for @wiki_dev;
  203. close($out_fh);
  204. $processed++;
  205. }
  206. print "\n=== ИТОГИ ===\n";
  207. print "Обработано файлов: $processed\n";
  208. print "Ошибок: $errors\n";
  209. print "Синхронизация завершена.\n";
  210. exit 0;
  211. sub api_call {
  212. my ($ua, $method, $url) = @_;
  213. my $req = HTTP::Request->new($method => $url);
  214. my $res = $ua->request($req);
  215. my $result = {};
  216. if (!$res->is_success) {
  217. $result->{error} = $res->status_line;
  218. return $result;
  219. }
  220. eval {
  221. $result->{data} = decode_json($res->decoded_content);
  222. };
  223. if ($@) {
  224. $result->{error} = "JSON parse error: $@";
  225. }
  226. return $result;
  227. }
  228. sub is_valid_ipv4 {
  229. my ($ip) = @_;
  230. return $ip =~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/ &&
  231. !grep { $_ > 255 } split(/\./, $ip);
  232. }
  233. sub wanted {
  234. my $filename = $File::Find::name;
  235. my $dev_name = basename($filename);
  236. if ($dev_name =~ /\.txt$/ && $dev_name =~ /^(Device|Switch|Ups|Sensor|Gateway|Router|Server|Bras)/) {
  237. $dev_name =~ s/\.txt$//;
  238. $content{$dev_name} = $filename;
  239. }
  240. return;
  241. }