update_wiki_eye.pl 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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 FindBin '$Bin';
  15. use lib "$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. # Инициализация HTTP-клиента
  34. my $ua = LWP::UserAgent->new(
  35. timeout => 60,
  36. agent => 'EyeWikiUpdater/1.0'
  37. );
  38. # Базовые параметры аутентификации для всех запросов
  39. my $auth_params = "api_key=" . uri_escape($api_key) . "&login=" . uri_escape($api_login);
  40. # === Получение пути к вики из таблицы config (id=61) ===
  41. my $config_record = api_call('GET', "$api_base?get_table_record&table=config&id=61&$auth_params");
  42. if (!$config_record || $config_record->{error}) {
  43. die "Ошибка: не удалось получить путь к вики из таблицы config (id=61)\n";
  44. }
  45. my $wiki_path = $config_record->{value};
  46. if (!$wiki_path || !-d $wiki_path) {
  47. die "Ошибка: путь к вики не настроен или директория не существует: $wiki_path\n";
  48. }
  49. print "Путь к вики: $wiki_path\n\n";
  50. # Поиск подходящих файлов
  51. my %content;
  52. find(\&wanted, $wiki_path);
  53. my $processed = 0;
  54. my $errors = 0;
  55. foreach my $fname (sort keys %content) {
  56. # Чтение файла
  57. open(my $fh, '<:encoding(UTF-8)', $content{$fname}) or do {
  58. warn "Не удалось открыть файл $content{$fname}: $!\n";
  59. $errors++;
  60. next;
  61. };
  62. my @lines = <$fh>;
  63. close($fh);
  64. chomp(@lines);
  65. # Извлечение IP из метаданных
  66. my $ip;
  67. foreach my $line (@lines) {
  68. if ($line =~ /\%META\:FIELD\{name="DeviceIP"/) {
  69. if ($line =~ /value="([0-9]{1,3}(?:\.[0-9]{1,3}){3})"/) {
  70. $ip = $1;
  71. last;
  72. }
  73. }
  74. }
  75. next unless $ip && is_valid_ipv4($ip);
  76. # Получение записи из user_auth по IP через API
  77. my $auth = api_call('GET', "$api_base/user_auth.php?get=user_auth&ip=" . uri_escape($ip) . "&$auth_params");
  78. if (!$auth || $auth->{error} || !$auth->{WikiName}) {
  79. next;
  80. }
  81. # Пропускаем шлюзы
  82. next if $auth->{WikiName} =~ /^Gateway/;
  83. print "Found: $auth->{ip} $auth->{mac} ";
  84. my ($device_name, $device_port);
  85. my $error_msg;
  86. # Определение типа устройства и получение родительских данных
  87. eval {
  88. if ($auth->{WikiName} =~ /^(Switch|Router)/) {
  89. # Для коммутаторов/маршрутизаторов: получаем данные через цепочку устройств
  90. my $device = api_call('GET', "$api_base/user_auth.php?get_table_list&table=devices&filter=" .
  91. uri_escape(encode_json({ ip => $ip })) . "&$auth_params");
  92. die "Unknown device" unless $device && ref($device) eq 'ARRAY' && @{$device} > 0;
  93. $device = $device->[0];
  94. # Получаем аплинк-порт
  95. my $uplink_ports = api_call('GET', "$api_base/user_auth.php?get_table_list&table=device_ports&filter=" .
  96. uri_escape(encode_json({ device_id => $device->{id}, uplink => 1 })) . "&$auth_params");
  97. die "Unknown connection" unless $uplink_ports && ref($uplink_ports) eq 'ARRAY' && @{$uplink_ports} > 0;
  98. my $parent_connect = $uplink_ports->[0];
  99. # Получаем целевой порт
  100. my $parent_port = api_call('GET', "$api_base/user_auth.php?get_table_record&table=device_ports&id=" .
  101. $parent_connect->{target_port_id} . "&$auth_params");
  102. die "Unknown port connection" unless $parent_port;
  103. # Получаем родительское устройство
  104. my $device_parent = api_call('GET', "$api_base/user_auth.php?get_table_record&table=devices&id=" .
  105. $parent_port->{device_id} . "&$auth_params");
  106. die "Unknown parent device" unless $device_parent;
  107. # Получаем авторизацию родительского устройства
  108. my $auth_parent = api_call('GET', "$api_base/user_auth.php?get=user_auth&ip=" .
  109. uri_escape($device_parent->{ip}) . "&$auth_params");
  110. die "Unknown auth for device" unless $auth_parent && $auth_parent->{WikiName};
  111. $device_name = $auth_parent->{WikiName};
  112. $device_port = $parent_port->{port};
  113. }
  114. else {
  115. # Для других устройств: получаем данные через соединения
  116. my $connections = api_call('GET', "$api_base/user_auth.php?get_table_list&table=connections&filter=" .
  117. uri_escape(encode_json({ auth_id => $auth->{id} })) . "&$auth_params");
  118. die "Unknown connection" unless $connections && ref($connections) eq 'ARRAY' && @{$connections} > 0;
  119. my $conn = $connections->[0];
  120. # Получаем порт устройства
  121. my $device_port_rec = api_call('GET', "$api_base/user_auth.php?get_table_record&table=device_ports&id=" .
  122. $conn->{port_id} . "&$auth_params");
  123. die "Unknown device port" unless $device_port_rec;
  124. # Получаем устройство
  125. my $device = api_call('GET', "$api_base/user_auth.php?get_table_record&table=devices&id=" .
  126. $device_port_rec->{device_id} . "&$auth_params");
  127. die "Unknown device" unless $device && $device->{user_id};
  128. # Получаем авторизацию устройства по user_id и IP
  129. my $device_auth_list = api_call('GET', "$api_base/user_auth.php?get_table_list&table=user_auth&filter=" .
  130. uri_escape(encode_json({ user_id => $device->{user_id}, ip => $device->{ip} })) . "&$auth_params");
  131. die "Unknown device auth" unless $device_auth_list && ref($device_auth_list) eq 'ARRAY' && @{$device_auth_list} > 0;
  132. my $device_auth = $device_auth_list->[0];
  133. die "Device auth has no WikiName" unless $device_auth->{WikiName};
  134. $device_name = $device_auth->{WikiName};
  135. $device_port = $device_port_rec->{port};
  136. }
  137. };
  138. if ($@) {
  139. $error_msg = $@;
  140. chomp($error_msg);
  141. print "Error: $error_msg\n";
  142. next;
  143. }
  144. # Подготовка обновленного содержимого файла
  145. my @wiki_dev;
  146. my %empty_fields = (parent => 1, parent_port => 1, mac => 1);
  147. foreach my $line (@lines) {
  148. if ($line =~ /\%META\:FIELD\{name="Parent"/) {
  149. $empty_fields{parent} = 0;
  150. if ($device_name) {
  151. push(@wiki_dev, '%META:FIELD{name="Parent" title="Parent" value="' . $device_name . '"}%');
  152. next;
  153. }
  154. }
  155. elsif ($line =~ /\%META\:FIELD\{name="ParentPort"/) {
  156. $empty_fields{parent_port} = 0;
  157. if ($device_port) {
  158. push(@wiki_dev, '%META:FIELD{name="ParentPort" title="Parent Port" value="' . $device_port . '"}%');
  159. next;
  160. }
  161. }
  162. elsif ($line =~ /\%META\:FIELD\{name="Mac"/) {
  163. $empty_fields{mac} = 0;
  164. if ($auth->{mac}) {
  165. push(@wiki_dev, '%META:FIELD{name="Mac" title="Mac" value="' . $auth->{mac} . '"}%');
  166. next;
  167. }
  168. }
  169. push(@wiki_dev, $line);
  170. }
  171. # Добавление отсутствующих полей
  172. if ($empty_fields{parent} && $device_name) {
  173. push(@wiki_dev, '%META:FIELD{name="Parent" title="Parent" value="' . $device_name . '"}%');
  174. }
  175. if ($empty_fields{parent_port} && $device_port) {
  176. push(@wiki_dev, '%META:FIELD{name="ParentPort" title="Parent Port" value="' . $device_port . '"}%');
  177. }
  178. if ($empty_fields{mac} && $auth->{mac}) {
  179. push(@wiki_dev, '%META:FIELD{name="Mac" title="Mac" value="' . $auth->{mac} . '"}%');
  180. }
  181. $device_name ||= 'None';
  182. $device_port ||= 'None';
  183. print "at $device_name $device_port\n";
  184. # Запись обновленного файла
  185. open(my $out_fh, '>:encoding(UTF-8)', $content{$fname}) or do {
  186. warn "Ошибка записи файла $content{$fname}: $!\n";
  187. $errors++;
  188. next;
  189. };
  190. print $out_fh $_ . "\n" for @wiki_dev;
  191. close($out_fh);
  192. $processed++;
  193. }
  194. print "\n=== ИТОГИ ===\n";
  195. print "Обработано файлов: $processed\n";
  196. print "Ошибок: $errors\n";
  197. print "Синхронизация завершена.\n";
  198. exit 0;
  199. # === Вспомогательные функции ===
  200. sub api_call {
  201. my ($method, $url) = @_;
  202. my $req = HTTP::Request->new($method => $url);
  203. my $res = $ua->request($req);
  204. return undef unless $res->is_success;
  205. my $content = decode_utf8($res->decoded_content);
  206. return decode_json($content);
  207. }
  208. sub is_valid_ipv4 {
  209. my ($ip) = @_;
  210. return $ip =~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/ &&
  211. !grep { $_ > 255 } split(/\./, $ip);
  212. }
  213. sub wanted {
  214. my $filename = $File::Find::name;
  215. my $dev_name = basename($filename);
  216. if ($dev_name =~ /\.txt$/ && $dev_name =~ /^(Device|Switch|Ups|Sensor|Gateway|Router|Server|Bras)/) {
  217. $dev_name =~ s/\.txt$//;
  218. $content{$dev_name} = $filename;
  219. }
  220. return;
  221. }