From 1dc84aa5ebf7b3b73e896fd641dcb9f9f86b2233 Mon Sep 17 00:00:00 2001 From: mariano Date: Wed, 20 May 2026 09:20:27 +0200 Subject: [PATCH] Stable --- AGENTS.md | 130 +++ AGENTS_OLD.MD | 66 ++ CHANGELOG.md | 39 + CORREZIONI_NECESSARIE.md | 77 ++ FIX_HISTORY.md | 18 + FIX_PERMISSIONS.sh | 27 + MEMORY.md | 269 +++++ README.md | 37 + ajax/server_test.php | 57 ++ composer.json | 23 + ecs.php | 21 + front/ajax.test.php | 59 ++ front/asset.form.php | 186 ++++ front/config.form.php | 32 + front/profile.form.php___ | 41 + front/server.form.php | 64 ++ front/server.php | 48 + front/server.test.php | 64 ++ front/server.view.php | 42 + front/server_test.ajax.php | 46 + front/test.button.php | 43 + gitflavio/COMMIT_EDITMSG | 1 + gitflavio/FETCH_HEAD | 1 + gitflavio/HEAD | 1 + gitflavio/ORIG_HEAD | 1 + gitflavio/config | 16 + gitflavio/description | 1 + gitflavio/hooks/applypatch-msg.sample | 15 + gitflavio/hooks/commit-msg.sample | 24 + gitflavio/hooks/fsmonitor-watchman.sample | 174 ++++ gitflavio/hooks/post-update.sample | 8 + gitflavio/hooks/pre-applypatch.sample | 14 + gitflavio/hooks/pre-commit.sample | 49 + gitflavio/hooks/pre-merge-commit.sample | 13 + gitflavio/hooks/pre-push.sample | 53 + gitflavio/hooks/pre-rebase.sample | 169 ++++ gitflavio/hooks/pre-receive.sample | 24 + gitflavio/hooks/prepare-commit-msg.sample | 42 + gitflavio/hooks/push-to-checkout.sample | 78 ++ gitflavio/hooks/sendemail-validate.sample | 77 ++ gitflavio/hooks/update.sample | 128 +++ gitflavio/index | Bin 0 -> 5227 bytes gitflavio/info/exclude | 6 + gitflavio/logs/HEAD | 12 + gitflavio/logs/refs/heads/dev | 7 + gitflavio/logs/refs/heads/main | 3 + gitflavio/logs/refs/remotes/origin/HEAD | 1 + gitflavio/logs/refs/remotes/origin/dev | 5 + gitflavio/logs/refs/remotes/origin/main | 1 + .../00/bfe6f899a6d08df50fe1151032f7d2432a23fc | Bin 0 -> 548 bytes .../00/d1fcaaa9d1d9244a8df60ddc4f46845d1a8456 | Bin 0 -> 2241 bytes .../00/d937889015edc16672a1aec414ccc184c743d7 | Bin 0 -> 1088 bytes .../01/c931072d5746de24e6323d3f9316655d814740 | Bin 0 -> 835 bytes .../05/aeab86ec3c5d3cdeac2e7cb0cdbce7af92a5bf | Bin 0 -> 64 bytes .../0d/8b64bd85c5cf7509028a7cf1cb1646ae9d20e3 | Bin 0 -> 1462 bytes .../0f/57c04a93e542f698d9ee8d5662296a10dd79f6 | Bin 0 -> 1043 bytes .../11/2ac1a3d31de40ed19e31ebe2a09969a91ca785 | Bin 0 -> 1266 bytes .../11/6523d7a24f610648183920620579d30b8e007f | Bin 0 -> 191 bytes .../13/a1fd4350238bfc24cfb8541036f0bee8e4ca05 | Bin 0 -> 1630 bytes .../14/e1027489784b717661588cb43caa419eec86bd | Bin 0 -> 1320 bytes .../17/18114462eb2d4b292baba4edc3c6f79f7c3ea3 | Bin 0 -> 606 bytes .../18/5b273ec42b62b8ae4f74e59eae70395cc88752 | Bin 0 -> 4528 bytes .../18/a77d83519ba3e8fd6a17a1b44acb63bc7a50cf | Bin 0 -> 706 bytes .../1b/210e4c7201ee826cb4f0e479e5486307e9bf7c | Bin 0 -> 5182 bytes .../1c/8552781a34629a6176355ba50be371ec5166a9 | 7 + .../1e/bb463168f36028ae98c61581e6c8ff953617d0 | Bin 0 -> 64 bytes .../1f/9710002aa168ed7592b58b9f2d9e1f3431d30c | Bin 0 -> 1424 bytes .../20/4ea8b1044799df5f383c9b7489ba8a876a7c03 | Bin 0 -> 70 bytes .../21/5277ac86967035797e47da0b5010a87ce0b98f | Bin 0 -> 71 bytes .../24/d8f24f885f3be2c2de581e768885b5caf90c1e | Bin 0 -> 331 bytes .../25/9e2ef94ae4f4aa5f93125a6551690a28af573b | Bin 0 -> 1632 bytes .../27/7ad43ceff67cfe1f3867301b6c3552917bcd8e | Bin 0 -> 56 bytes .../27/aac99d1555a75d373756d8727afeefd6b69376 | 3 + .../31/1accf4bc2817584dd35d611c7483bfd1f9d70d | Bin 0 -> 173 bytes .../31/a95026c46ca2c322f0f6770300750573cc58d9 | Bin 0 -> 1060 bytes .../35/62526c1df22fc0f8d6b6e9463a3adff126413b | Bin 0 -> 672 bytes .../3a/38bc98a9b989f99c685dacc9a620429c8af5a7 | Bin 0 -> 61 bytes .../3a/3c4d169ec5ca603e940c6ee57305c4200867df | Bin 0 -> 1447 bytes .../3a/ec748a00e2437143afbcce9789e9e1c440e86d | Bin 0 -> 115 bytes .../3b/25f0d2e7ba99cc57fd06590ccad42117d56e1a | Bin 0 -> 499 bytes .../3d/47b597ecf2e93c4020cdeb910a06b99f02ee39 | 1 + .../3d/5eba338b0383acac7cbc8bf39ef882ebb52945 | Bin 0 -> 367 bytes .../41/83ad2c64664c3b69fe68ed12374393ad025e2f | 5 + .../42/dfeff5d2b6452efa18cff9bc55ef86c254e997 | Bin 0 -> 1412 bytes .../43/018fccb86a1aa4b66a54a0489bc8fe5e664916 | Bin 0 -> 5171 bytes .../45/4285ea080576f5db99ff0cd83dd6a7717241a9 | 6 + .../46/15c1d7cc58efbcfff10d8325a03934f540c569 | Bin 0 -> 1979 bytes .../46/cd427d457cecc5889a92440131ce63d7a92532 | Bin 0 -> 259 bytes .../47/f6b90efbf694365d7831eb615591c109ab77b5 | Bin 0 -> 730 bytes .../4b/3ededa083d208e7ce6e42b8632d295735b2982 | Bin 0 -> 189 bytes .../4f/21aa1aca615a1fea2786d0c759a4626aba551d | Bin 0 -> 1494 bytes .../50/5476e9934cac4c29eb6a086daf07671e01517f | Bin 0 -> 1461 bytes .../52/024d4228f1a54de35513bd1a14dfaeff1ddeb9 | Bin 0 -> 339 bytes .../52/4523b4454b2c3bc0e88a948fd4789af7bbdae8 | Bin 0 -> 3722 bytes .../56/eb4d8b0f5bf018d99412161cf1346e29540fe8 | Bin 0 -> 2020 bytes .../57/41929666cdf91205979ab26c6b6ccdf8a6979a | Bin 0 -> 295 bytes .../61/b7171b1db577c2de9d43dadd5cc1fecce774dc | Bin 0 -> 634 bytes .../61/ccf6d74902ab1b4ed510d8dad7df2fb80ff875 | Bin 0 -> 656 bytes .../61/dd7aa53d6ebb43ca028ef9d2706efa7bd9ee68 | Bin 0 -> 4038 bytes .../63/ea5c4d83858585a4d32a54f12e00a4c19891cf | Bin 0 -> 1049 bytes .../64/93631fb88f9a570c95cfab46b89a144a9e9503 | 3 + .../65/790e7d770de478c167d9435a4b7858346f7072 | Bin 0 -> 769 bytes .../67/9b0dafccecfb1a9fa9a341853f524b4b52abbf | Bin 0 -> 2948 bytes .../68/22b2bcae642863ca6e0115924af954e14ccff8 | Bin 0 -> 95 bytes .../70/dff38c872847e1c23ef1c77594086839a2f985 | Bin 0 -> 1206 bytes .../79/2e374a39495bd4ce54bdb1efab74ef749b90b8 | Bin 0 -> 115 bytes .../79/32e201a26405962f2f20e3d55c57236c152f91 | Bin 0 -> 689 bytes .../7c/48ca70eca8ed15e3a8ea9f9d54c1a465501a40 | 2 + .../7d/28b1fb6885c2730f3eb871bc63936c1dc5ebb2 | Bin 0 -> 1003 bytes .../7f/c40925047551dbc6f31069af430848dc862bb2 | Bin 0 -> 155 bytes .../88/1b6ebdd497616c0eaf0688fdb79edc758a396b | Bin 0 -> 359 bytes .../90/a894f2dbef8d9efdeb61db05f659cd7078e4da | Bin 0 -> 424 bytes .../98/dc9fafeb52a5c6d0130be29d5716133e086821 | Bin 0 -> 154 bytes .../9a/73f51de5135da21bbc52ca183bf1e9a48cd0f2 | 2 + .../9b/126e9f343807b3e4e2f4b99a1fe4336007512f | Bin 0 -> 1351 bytes .../9b/419ab1e2420866e6abbb7bde1e53713f021514 | Bin 0 -> 394 bytes .../9c/730d7a658305cc598ea196667e6dadc80389b3 | Bin 0 -> 1515 bytes .../a0/09d2026d6ca35505c7a633ca47f58a500ff22d | Bin 0 -> 71 bytes .../a0/e5cf00708a67b4a8c24095983f946518631d86 | 1 + .../a3/011e81556e83afc2c1532d34c2f716cbbc3c00 | Bin 0 -> 548 bytes .../a8/ab1151db2e98478a1b23997064651e00869362 | Bin 0 -> 7480 bytes .../a9/0e9d8c9a116fe031f668b5eb45aab856b51f1a | Bin 0 -> 360 bytes .../a9/4bc3b018e6fac2225f73c4fb627f77e2ddf4e6 | Bin 0 -> 802 bytes .../ac/388d55f65eb170a37b388b33527bfcd8362edd | Bin 0 -> 126 bytes .../b0/0c568aa35f2f52dc581f4c17fb444d02c3410a | Bin 0 -> 56 bytes .../b1/45d5cc6f46344f5901e4c22eb4a2a17463cea3 | Bin 0 -> 239 bytes .../b3/66f2ae913116428c52594f39f6985fdede6283 | Bin 0 -> 701 bytes .../b7/bffdd64ff74d4165aa49ecf2fa49006d0f9334 | Bin 0 -> 172 bytes .../b9/2df0384658e96222ff592605ad63b8da5b2251 | Bin 0 -> 346 bytes .../b9/e4a6d1083532b8c383ed2e3eea7be67ca93e1d | Bin 0 -> 7404 bytes .../ba/3596df07d580af1ec86456251d1fa22a438c78 | Bin 0 -> 880 bytes .../bc/c2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 | 2 + .../c2/5da0a8ba00702846b95750dfb1bc8213891515 | Bin 0 -> 1400 bytes .../c3/bc354ed51f43a0e22b08170a0ec7989a191dcf | Bin 0 -> 862 bytes .../c3/c45feb44e821bc8970aa86d39378d7ffbb2e65 | Bin 0 -> 108 bytes .../c4/99fd993f6fd97f81f66884726008ad9d58b612 | Bin 0 -> 753 bytes .../c6/a533649e8b8fa17ab9edd519bc838be7cc0f8b | Bin 0 -> 586 bytes .../c9/fd16c541466c9c5a4148ae75b2a0d1f47f466e | Bin 0 -> 63 bytes .../d1/54409013d1944a2052775807d2bc4063178d20 | Bin 0 -> 6906 bytes .../d6/2f168a7ab0e42e573296c8814047441a861605 | Bin 0 -> 126 bytes .../d6/57b40685bcee4c0783c793d487e8c1bab223e0 | Bin 0 -> 753 bytes .../d8/db92be2d9783d478afafd00b3a5ef014055f5b | Bin 0 -> 1786 bytes .../db/0bb8cad5e4e4749f9bcf95c7da066d9339eec0 | 6 + .../de/e63d0b4ae892fcc836df19d655ef78fba0df91 | Bin 0 -> 753 bytes .../e0/83fa81a6c387fc8ac548e8bc399b8335d781ef | Bin 0 -> 359 bytes .../e3/a5f4a180ecfd4e6b68f783989b3b9789b26fb6 | Bin 0 -> 126 bytes .../e4/e4b5179c616789e7dbdd52cb86a5becd0a9c14 | Bin 0 -> 1242 bytes .../e5/0adaeacd7429ac492c7f1a7213055c90458eb7 | 4 + .../e6/bb5deaba73fdea1c97b9c09bc810a9c92fa72d | Bin 0 -> 648 bytes .../e7/b85297d4141c5e4190b68433d8e1cd5664c6a3 | Bin 0 -> 753 bytes .../f0/0e847952b9239ced8dccdc0af19b68ebfb7d3d | Bin 0 -> 623 bytes .../f1/f8001c76b95c16e22b018bb0e02fca8dece54f | Bin 0 -> 188 bytes .../f6/727eda1e7bfd0a868ff93615a04b44b928c76d | Bin 0 -> 5014 bytes .../f8/5893a04200dc8c30e75955bb12d4905110d2ff | Bin 0 -> 802 bytes .../fc/e0dd97ed72cbc3b85c8310bfee8247bcf6d1b5 | Bin 0 -> 824 bytes .../ff/d908df1af4d233249c5bf9a88dff36419a192c | Bin 0 -> 1282 bytes ...f64c98dc9785f95395d0429bffba92412a8d22.idx | Bin 0 -> 3284 bytes ...64c98dc9785f95395d0429bffba92412a8d22.pack | Bin 0 -> 46673 bytes ...f64c98dc9785f95395d0429bffba92412a8d22.rev | Bin 0 -> 368 bytes gitflavio/opencode | 1 + gitflavio/packed-refs | 3 + gitflavio/refs/heads/dev | 1 + gitflavio/refs/heads/main | 1 + gitflavio/refs/remotes/origin/HEAD | 1 + gitflavio/refs/remotes/origin/dev | 1 + gitflavio/refs/remotes/origin/main | 1 + hook.php | 71 ++ install/install.php | 348 +++++++ install/mysql/plugin_urbackup-empty.sql | 69 ++ install/uninstall.php | 59 ++ js/urbackup.js | 87 ++ locales/de_DE.mo | Bin 0 -> 3126 bytes locales/de_DE.po | 158 +++ locales/en_GB.mo | Bin 0 -> 2979 bytes locales/en_GB.po | 158 +++ locales/it_IT.mo | Bin 0 -> 3083 bytes locales/it_IT.po | 158 +++ phpstan.neon | 12 + public/css/urbackup.css | 12 + public/js/urbackup.js | 82 ++ remove_deprecated_files.sh | 11 + setup.php | 178 ++++ src/AssetTab.php | 913 +++++++++++++++++ src/Command/TestApiCommand.php | 56 ++ src/Config.php | 414 ++++++++ src/Controller/AssetController.php | 193 ++++ src/Controller/ConfigController.php | 45 + src/Controller/ServerController.php | 109 ++ src/LocationHelper.php | 119 +++ src/MassiveAction.php | 264 +++++ src/Profile.php | 246 +++++ src/Server.php | 942 ++++++++++++++++++ src/ServerAsset.php | 291 ++++++ src/UrbackupApiClient.php | 766 ++++++++++++++ templates/asset/asset_tab.html.twig | 77 ++ templates/config/config.html.twig | 39 + templates/profile.html.twig | 34 + templates/server/server_form.html.twig | 118 +++ templates/server/server_list.html.twig | 51 + 199 files changed, 8444 insertions(+) create mode 100644 AGENTS.md create mode 100644 AGENTS_OLD.MD create mode 100644 CHANGELOG.md create mode 100644 CORREZIONI_NECESSARIE.md create mode 100644 FIX_HISTORY.md create mode 100755 FIX_PERMISSIONS.sh create mode 100644 MEMORY.md create mode 100644 README.md create mode 100644 ajax/server_test.php create mode 100644 composer.json create mode 100644 ecs.php create mode 100644 front/ajax.test.php create mode 100644 front/asset.form.php create mode 100644 front/config.form.php create mode 100644 front/profile.form.php___ create mode 100644 front/server.form.php create mode 100644 front/server.php create mode 100644 front/server.test.php create mode 100644 front/server.view.php create mode 100644 front/server_test.ajax.php create mode 100644 front/test.button.php create mode 100644 gitflavio/COMMIT_EDITMSG create mode 100644 gitflavio/FETCH_HEAD create mode 100644 gitflavio/HEAD create mode 100644 gitflavio/ORIG_HEAD create mode 100644 gitflavio/config create mode 100644 gitflavio/description create mode 100755 gitflavio/hooks/applypatch-msg.sample create mode 100755 gitflavio/hooks/commit-msg.sample create mode 100755 gitflavio/hooks/fsmonitor-watchman.sample create mode 100755 gitflavio/hooks/post-update.sample create mode 100755 gitflavio/hooks/pre-applypatch.sample create mode 100755 gitflavio/hooks/pre-commit.sample create mode 100755 gitflavio/hooks/pre-merge-commit.sample create mode 100755 gitflavio/hooks/pre-push.sample create mode 100755 gitflavio/hooks/pre-rebase.sample create mode 100755 gitflavio/hooks/pre-receive.sample create mode 100755 gitflavio/hooks/prepare-commit-msg.sample create mode 100755 gitflavio/hooks/push-to-checkout.sample create mode 100755 gitflavio/hooks/sendemail-validate.sample create mode 100755 gitflavio/hooks/update.sample create mode 100644 gitflavio/index create mode 100644 gitflavio/info/exclude create mode 100644 gitflavio/logs/HEAD create mode 100644 gitflavio/logs/refs/heads/dev create mode 100644 gitflavio/logs/refs/heads/main create mode 100644 gitflavio/logs/refs/remotes/origin/HEAD create mode 100644 gitflavio/logs/refs/remotes/origin/dev create mode 100644 gitflavio/logs/refs/remotes/origin/main create mode 100644 gitflavio/objects/00/bfe6f899a6d08df50fe1151032f7d2432a23fc create mode 100644 gitflavio/objects/00/d1fcaaa9d1d9244a8df60ddc4f46845d1a8456 create mode 100644 gitflavio/objects/00/d937889015edc16672a1aec414ccc184c743d7 create mode 100644 gitflavio/objects/01/c931072d5746de24e6323d3f9316655d814740 create mode 100644 gitflavio/objects/05/aeab86ec3c5d3cdeac2e7cb0cdbce7af92a5bf create mode 100644 gitflavio/objects/0d/8b64bd85c5cf7509028a7cf1cb1646ae9d20e3 create mode 100644 gitflavio/objects/0f/57c04a93e542f698d9ee8d5662296a10dd79f6 create mode 100644 gitflavio/objects/11/2ac1a3d31de40ed19e31ebe2a09969a91ca785 create mode 100644 gitflavio/objects/11/6523d7a24f610648183920620579d30b8e007f create mode 100644 gitflavio/objects/13/a1fd4350238bfc24cfb8541036f0bee8e4ca05 create mode 100644 gitflavio/objects/14/e1027489784b717661588cb43caa419eec86bd create mode 100644 gitflavio/objects/17/18114462eb2d4b292baba4edc3c6f79f7c3ea3 create mode 100644 gitflavio/objects/18/5b273ec42b62b8ae4f74e59eae70395cc88752 create mode 100644 gitflavio/objects/18/a77d83519ba3e8fd6a17a1b44acb63bc7a50cf create mode 100644 gitflavio/objects/1b/210e4c7201ee826cb4f0e479e5486307e9bf7c create mode 100644 gitflavio/objects/1c/8552781a34629a6176355ba50be371ec5166a9 create mode 100644 gitflavio/objects/1e/bb463168f36028ae98c61581e6c8ff953617d0 create mode 100644 gitflavio/objects/1f/9710002aa168ed7592b58b9f2d9e1f3431d30c create mode 100644 gitflavio/objects/20/4ea8b1044799df5f383c9b7489ba8a876a7c03 create mode 100644 gitflavio/objects/21/5277ac86967035797e47da0b5010a87ce0b98f create mode 100644 gitflavio/objects/24/d8f24f885f3be2c2de581e768885b5caf90c1e create mode 100644 gitflavio/objects/25/9e2ef94ae4f4aa5f93125a6551690a28af573b create mode 100644 gitflavio/objects/27/7ad43ceff67cfe1f3867301b6c3552917bcd8e create mode 100644 gitflavio/objects/27/aac99d1555a75d373756d8727afeefd6b69376 create mode 100644 gitflavio/objects/31/1accf4bc2817584dd35d611c7483bfd1f9d70d create mode 100644 gitflavio/objects/31/a95026c46ca2c322f0f6770300750573cc58d9 create mode 100644 gitflavio/objects/35/62526c1df22fc0f8d6b6e9463a3adff126413b create mode 100644 gitflavio/objects/3a/38bc98a9b989f99c685dacc9a620429c8af5a7 create mode 100644 gitflavio/objects/3a/3c4d169ec5ca603e940c6ee57305c4200867df create mode 100644 gitflavio/objects/3a/ec748a00e2437143afbcce9789e9e1c440e86d create mode 100644 gitflavio/objects/3b/25f0d2e7ba99cc57fd06590ccad42117d56e1a create mode 100644 gitflavio/objects/3d/47b597ecf2e93c4020cdeb910a06b99f02ee39 create mode 100644 gitflavio/objects/3d/5eba338b0383acac7cbc8bf39ef882ebb52945 create mode 100644 gitflavio/objects/41/83ad2c64664c3b69fe68ed12374393ad025e2f create mode 100644 gitflavio/objects/42/dfeff5d2b6452efa18cff9bc55ef86c254e997 create mode 100644 gitflavio/objects/43/018fccb86a1aa4b66a54a0489bc8fe5e664916 create mode 100644 gitflavio/objects/45/4285ea080576f5db99ff0cd83dd6a7717241a9 create mode 100644 gitflavio/objects/46/15c1d7cc58efbcfff10d8325a03934f540c569 create mode 100644 gitflavio/objects/46/cd427d457cecc5889a92440131ce63d7a92532 create mode 100644 gitflavio/objects/47/f6b90efbf694365d7831eb615591c109ab77b5 create mode 100644 gitflavio/objects/4b/3ededa083d208e7ce6e42b8632d295735b2982 create mode 100644 gitflavio/objects/4f/21aa1aca615a1fea2786d0c759a4626aba551d create mode 100644 gitflavio/objects/50/5476e9934cac4c29eb6a086daf07671e01517f create mode 100644 gitflavio/objects/52/024d4228f1a54de35513bd1a14dfaeff1ddeb9 create mode 100644 gitflavio/objects/52/4523b4454b2c3bc0e88a948fd4789af7bbdae8 create mode 100644 gitflavio/objects/56/eb4d8b0f5bf018d99412161cf1346e29540fe8 create mode 100644 gitflavio/objects/57/41929666cdf91205979ab26c6b6ccdf8a6979a create mode 100644 gitflavio/objects/61/b7171b1db577c2de9d43dadd5cc1fecce774dc create mode 100644 gitflavio/objects/61/ccf6d74902ab1b4ed510d8dad7df2fb80ff875 create mode 100644 gitflavio/objects/61/dd7aa53d6ebb43ca028ef9d2706efa7bd9ee68 create mode 100644 gitflavio/objects/63/ea5c4d83858585a4d32a54f12e00a4c19891cf create mode 100644 gitflavio/objects/64/93631fb88f9a570c95cfab46b89a144a9e9503 create mode 100644 gitflavio/objects/65/790e7d770de478c167d9435a4b7858346f7072 create mode 100644 gitflavio/objects/67/9b0dafccecfb1a9fa9a341853f524b4b52abbf create mode 100644 gitflavio/objects/68/22b2bcae642863ca6e0115924af954e14ccff8 create mode 100644 gitflavio/objects/70/dff38c872847e1c23ef1c77594086839a2f985 create mode 100644 gitflavio/objects/79/2e374a39495bd4ce54bdb1efab74ef749b90b8 create mode 100644 gitflavio/objects/79/32e201a26405962f2f20e3d55c57236c152f91 create mode 100644 gitflavio/objects/7c/48ca70eca8ed15e3a8ea9f9d54c1a465501a40 create mode 100644 gitflavio/objects/7d/28b1fb6885c2730f3eb871bc63936c1dc5ebb2 create mode 100644 gitflavio/objects/7f/c40925047551dbc6f31069af430848dc862bb2 create mode 100644 gitflavio/objects/88/1b6ebdd497616c0eaf0688fdb79edc758a396b create mode 100644 gitflavio/objects/90/a894f2dbef8d9efdeb61db05f659cd7078e4da create mode 100644 gitflavio/objects/98/dc9fafeb52a5c6d0130be29d5716133e086821 create mode 100644 gitflavio/objects/9a/73f51de5135da21bbc52ca183bf1e9a48cd0f2 create mode 100644 gitflavio/objects/9b/126e9f343807b3e4e2f4b99a1fe4336007512f create mode 100644 gitflavio/objects/9b/419ab1e2420866e6abbb7bde1e53713f021514 create mode 100644 gitflavio/objects/9c/730d7a658305cc598ea196667e6dadc80389b3 create mode 100644 gitflavio/objects/a0/09d2026d6ca35505c7a633ca47f58a500ff22d create mode 100644 gitflavio/objects/a0/e5cf00708a67b4a8c24095983f946518631d86 create mode 100644 gitflavio/objects/a3/011e81556e83afc2c1532d34c2f716cbbc3c00 create mode 100644 gitflavio/objects/a8/ab1151db2e98478a1b23997064651e00869362 create mode 100644 gitflavio/objects/a9/0e9d8c9a116fe031f668b5eb45aab856b51f1a create mode 100644 gitflavio/objects/a9/4bc3b018e6fac2225f73c4fb627f77e2ddf4e6 create mode 100644 gitflavio/objects/ac/388d55f65eb170a37b388b33527bfcd8362edd create mode 100644 gitflavio/objects/b0/0c568aa35f2f52dc581f4c17fb444d02c3410a create mode 100644 gitflavio/objects/b1/45d5cc6f46344f5901e4c22eb4a2a17463cea3 create mode 100644 gitflavio/objects/b3/66f2ae913116428c52594f39f6985fdede6283 create mode 100644 gitflavio/objects/b7/bffdd64ff74d4165aa49ecf2fa49006d0f9334 create mode 100644 gitflavio/objects/b9/2df0384658e96222ff592605ad63b8da5b2251 create mode 100644 gitflavio/objects/b9/e4a6d1083532b8c383ed2e3eea7be67ca93e1d create mode 100644 gitflavio/objects/ba/3596df07d580af1ec86456251d1fa22a438c78 create mode 100644 gitflavio/objects/bc/c2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 create mode 100644 gitflavio/objects/c2/5da0a8ba00702846b95750dfb1bc8213891515 create mode 100644 gitflavio/objects/c3/bc354ed51f43a0e22b08170a0ec7989a191dcf create mode 100644 gitflavio/objects/c3/c45feb44e821bc8970aa86d39378d7ffbb2e65 create mode 100644 gitflavio/objects/c4/99fd993f6fd97f81f66884726008ad9d58b612 create mode 100644 gitflavio/objects/c6/a533649e8b8fa17ab9edd519bc838be7cc0f8b create mode 100644 gitflavio/objects/c9/fd16c541466c9c5a4148ae75b2a0d1f47f466e create mode 100644 gitflavio/objects/d1/54409013d1944a2052775807d2bc4063178d20 create mode 100644 gitflavio/objects/d6/2f168a7ab0e42e573296c8814047441a861605 create mode 100644 gitflavio/objects/d6/57b40685bcee4c0783c793d487e8c1bab223e0 create mode 100644 gitflavio/objects/d8/db92be2d9783d478afafd00b3a5ef014055f5b create mode 100644 gitflavio/objects/db/0bb8cad5e4e4749f9bcf95c7da066d9339eec0 create mode 100644 gitflavio/objects/de/e63d0b4ae892fcc836df19d655ef78fba0df91 create mode 100644 gitflavio/objects/e0/83fa81a6c387fc8ac548e8bc399b8335d781ef create mode 100644 gitflavio/objects/e3/a5f4a180ecfd4e6b68f783989b3b9789b26fb6 create mode 100644 gitflavio/objects/e4/e4b5179c616789e7dbdd52cb86a5becd0a9c14 create mode 100644 gitflavio/objects/e5/0adaeacd7429ac492c7f1a7213055c90458eb7 create mode 100644 gitflavio/objects/e6/bb5deaba73fdea1c97b9c09bc810a9c92fa72d create mode 100644 gitflavio/objects/e7/b85297d4141c5e4190b68433d8e1cd5664c6a3 create mode 100644 gitflavio/objects/f0/0e847952b9239ced8dccdc0af19b68ebfb7d3d create mode 100644 gitflavio/objects/f1/f8001c76b95c16e22b018bb0e02fca8dece54f create mode 100644 gitflavio/objects/f6/727eda1e7bfd0a868ff93615a04b44b928c76d create mode 100644 gitflavio/objects/f8/5893a04200dc8c30e75955bb12d4905110d2ff create mode 100644 gitflavio/objects/fc/e0dd97ed72cbc3b85c8310bfee8247bcf6d1b5 create mode 100644 gitflavio/objects/ff/d908df1af4d233249c5bf9a88dff36419a192c create mode 100644 gitflavio/objects/pack/pack-16f64c98dc9785f95395d0429bffba92412a8d22.idx create mode 100644 gitflavio/objects/pack/pack-16f64c98dc9785f95395d0429bffba92412a8d22.pack create mode 100644 gitflavio/objects/pack/pack-16f64c98dc9785f95395d0429bffba92412a8d22.rev create mode 100644 gitflavio/opencode create mode 100644 gitflavio/packed-refs create mode 100644 gitflavio/refs/heads/dev create mode 100644 gitflavio/refs/heads/main create mode 100644 gitflavio/refs/remotes/origin/HEAD create mode 100644 gitflavio/refs/remotes/origin/dev create mode 100644 gitflavio/refs/remotes/origin/main create mode 100644 hook.php create mode 100644 install/install.php create mode 100644 install/mysql/plugin_urbackup-empty.sql create mode 100644 install/uninstall.php create mode 100644 js/urbackup.js create mode 100644 locales/de_DE.mo create mode 100644 locales/de_DE.po create mode 100644 locales/en_GB.mo create mode 100644 locales/en_GB.po create mode 100644 locales/it_IT.mo create mode 100644 locales/it_IT.po create mode 100644 phpstan.neon create mode 100644 public/css/urbackup.css create mode 100644 public/js/urbackup.js create mode 100755 remove_deprecated_files.sh create mode 100644 setup.php create mode 100644 src/AssetTab.php create mode 100644 src/Command/TestApiCommand.php create mode 100644 src/Config.php create mode 100644 src/Controller/AssetController.php create mode 100644 src/Controller/ConfigController.php create mode 100644 src/Controller/ServerController.php create mode 100644 src/LocationHelper.php create mode 100644 src/MassiveAction.php create mode 100644 src/Profile.php create mode 100644 src/Server.php create mode 100644 src/ServerAsset.php create mode 100644 src/UrbackupApiClient.php create mode 100644 templates/asset/asset_tab.html.twig create mode 100644 templates/config/config.html.twig create mode 100644 templates/profile.html.twig create mode 100644 templates/server/server_form.html.twig create mode 100644 templates/server/server_list.html.twig diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..524523b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,130 @@ +# AGENTS.md - AI Assistant per lo Sviluppo Plugin GLPI 11.x + +## 🎯 Ruolo e Obiettivo +Sei un **Senior GLPI Plugin Architect & PHP/Symfony Engineer**. Il tuo compito è progettare, generare e validare plugin per **GLPI 11.0.6 e successivi**, garantendo: +- ✅ Compatibilità 100% con GLPI 11.x (architettura moderna, namespacing, composer) +- ✅ Esecuzione su **PHP 8.3 e PHP 8.4** con strict mode attivo +- ✅ Integrazione corretta con i componenti **Symfony** esposti da GLPI core +- ✅ Sicurezza, performance e manutenibilità enterprise-grade +- ✅ Codice pronto per il Marketplace GLPI e deployment production +--- + +## 📜 Direttive Fondamentali +1. **Nessuna supposizione**: Usa solo API, classi e hook documentati per GLPI 11.0.6+. Se un'API è incerta, richiedi conferma o fornisci fallback compatibili. +2. **Ciclo di vita rigoroso**: Rispetta obbligatoriamente `plugin_init_*`, `plugin_install_*`, `plugin_upgrade_*`, `plugin_uninstall_*`, `plugin_version_*`. +3. **Namespacing & Autoloading**: Tutte le classi devono risiedere in `src/` con namespace `Plugin\\`. Usa `composer.json` PSR-4. +4. **Strict PHP 8.3/8.4**: `declare(strict_types=1);` in ogni file. Usa typed properties, `readonly` classi, `#[\Override]`, `match`, enums, e nuove funzioni PHP 8.4 (`json_validate()`, `str_increment()`, ecc.) solo dove compatibili. +5. **Niente framework Symfony completo**: Usa esclusivamente i componenti già caricati da GLPI core (`symfony/console`, `symfony/http-foundation`, `symfony/validator`, `symfony/routing`, `symfony/cache`). Non includere `symfony/symfony` o bundle esterni. +6. **Sicurezza prima di tutto**: CSRF token obbligatorio per POST/AJAX, prepared statements sempre, escape output (`Html::entities_deep()`), validazione input con Symfony Validator o GLPI native, controllo diritti (`Session::haveRight()`). +7. **Memoria**:dopo ogni modifica funzionante scrivi il file MEMORY.md e rileggi AGENTS.md +--- + +## 🛠 Stack Tecnologico e Compatibilità +| Componente | Versione/Requisito | Note | +|------------|-------------------|------| +| **GLPI** | `>= 11.0.6` | Verifica `defined('GLPI_VERSION')` e `version_compare()` in `setup.php` | +| **PHP** | `8.3.x` o `8.4.x` | `strict_types=1`, JIT abilitato, nessuna funzione deprecata | +| **Database** | MySQL/MariaDB `10.5+` | Usa `$DB->request()`, `QueryExpression`, mai SQL raw non parametrizzato | +| **Symfony** | Componenti integrati in GLPI 11 | Autoloading via GLPI, nessun composer require esterno | +| **Frontend** | Twig (compatibile GLPI), JS vanilla/Vite, CSS/SCSS | Template in `templates/`, AJAX in `ajax/` | +| **Testing** | PHPUnit 10+, PHPStan 8+/Psalm strict | Mock di `$DB`, `$_SESSION`, `Auth`, `Session` | + +--- + +## 🏗 Architettura Plugin GLPI 11.0.6+ +``` +plugin_/ +├── composer.json # PSR-4, dipendenze lockate, no symfony/symfony +├── setup.php # Metadati, check versione, hook init +├── hook.php # install, upgrade, uninstall, data injection +├── plugin.xml # Marketplace metadata (opzionale) +├── src/ # Classi namespaced Plugin\\ +│ ├── Controller/ +│ ├── Entity/ +│ ├── Service/ +│ └── Validator/ +├── templates/ # Twig compatibili GLPI +├── ajax/ # Endpoint PHP con CSRF & permessi +├── install/ # Migrazioni SQL versionate +├── locales/ # File .po/.mo per i18n +├── css/ & js/ # Asset frontend +└── README.md # Istruzioni installazione, requisiti, changelog +``` + +### Hook Essenziali (`hook.php`) +- `plugin_install_()`: Crea tabelle, configura diritti, registra classi +- `plugin_upgrade_($version)`: Migrazione step-by-step con controllo versione DB +- `plugin_uninstall_()`: Drop tabelle, pulizia diritti, rimozione config +- `plugin_datainjection_populate_()`: Supporto DataInjection (opzionale) + +--- + +## 🔄 Workflow di Sviluppo (Output Obbligatorio dell'IA) +Per ogni richiesta, l'IA deve restituire: +1. 📁 **Struttura ad albero** completa del plugin +2. 📄 `setup.php` con check versione GLPI, namespace, metadata marketplace +3. 🔌 `hook.php` con install/upgrade/uninstall robusti e transazionali +4. 🧩 Classi `src/` con DI, validazione, logging (`Glpi\Log` o `Toolbox::logDebug()`) +5. 🌐 Endpoint `ajax/` con CSRF, `Session::checkCSRF()`, output JSON strutturato +6. 🗃️ Migrazioni `install/` con versioning e rollback sicuro +7. 📦 `composer.json` con autoloading PSR-4 e dipendenze necessarie +8. 🧪 Istruzioni di test, comandi CLI e troubleshooting +9. ✅ Checklist di validazione pre-consegna + +--- + +## ✅ Standard di Qualità e Sicurezza +- **PSR-12 / PSR-4** applicati rigorosamente +- **PHPDoc** completo per classi pubbliche e metodi +- **Nessun warning/deprecation** PHP 8.3/8.4 o GLPI 11 +- **Cache**: Usa `Glpi\Cache` o `symfony/cache` dove appropriato +- **Logging**: `Glpi\Log\Logger` o `Toolbox::logDebug()` per trace +- **i18n**: Tutte le stringhe utente in `__()` e `__n()`, file `.pot` generabili +- **Permessi**: `Session::haveRight('plugin_', READ/UPDATE/CREATE/DELETE)` +- **Output**: Escape HTML, JSON con `header('Content-Type: application/json')` + +--- + +## 🧪 Testing e Validazione +L'IA deve includere o suggerire: +- ✅ Test unitari PHPUnit con mock di `$DB`, `Session`, `Auth` +- ✅ Test CSRF, SQL injection, XSS, privilege escalation +- ✅ Validazione input con `Symfony\Component\Validator` +- ✅ Compatibilità PHP 8.3/8.4 verificata con `php -l` e runtime check +- ✅ Istruzioni per ambiente di test Docker (`docker-glpi` ufficiale) +- ✅ Comandi: `php bin/console glpi:plugin:install `, `glpi:plugin:activate` + +--- + +## 🤖 Comportamento dell'IA +- 🗣️ Rispondi in **italiano tecnico chiaro**, senza fronzoli +- 📦 Fornisci **codice completo**, non snippet parziali o placeholder +- 🔍 Spiega **scelte architetturali**, alternative e trade-off +- ⚠️ Segnala **incompatibilità note** con GLPI 11.x o PHP 8.4 +- 📝 Usa blocchi markdown con linguaggio specifico (`php`, `json`, `sql`, `bash`) +- ❌ Non inventare API GLPI non documentate; se incerto, chiedi conferma o fornisci fallback +- 📋 Includi sempre: struttura, comandi installazione, troubleshooting, checklist finale + +### Checklist Pre-Consegna (Obbligatoria) +- [ ] `declare(strict_types=1);` in ogni file PHP +- [ ] Namespace `Plugin\\` e PSR-4 corretto +- [ ] Check versione GLPI 11.0.6+ in `setup.php` +- [ ] CSRF e permessi su ogni POST/AJAX +- [ ] Query parametrizzate o `$DB->request()` +- [ ] Output escaped e loggato +- [ ] Nessun uso di API deprecate GLPI 11 +- [ ] Compatibilità PHP 8.3/8.4 verificata +- [ ] Istruzioni installazione e test incluse + +--- + +## 📚 Risorse e Riferimenti Ufficiali +- 📘 [GLPI 11 Plugin Development Guide](https://glpi-project.org/documentation/) +- 🔗 [GLPI GitHub - Plugin Examples](https://github.com/glpi-project) +- 🐘 [PHP 8.3/8.4 Migration & New Features](https://www.php.net/manual/en/migration83.php) +- 🧩 [Symfony Components (compatibili con GLPI)](https://symfony.com/components) +- 🔍 [PHPStan/Psalm Config for GLPI Plugins](https://phpstan.org/) +- 🐳 [Official GLPI Docker for Testing](https://github.com/glpi-project/docker) + +--- +> ⚙️ **Nota per l'IA**: Questo file è un system prompt operativo. Ogni risposta deve aderire rigidamente a queste direttive. Se un requisito confligge con GLPI 11.x o PHP 8.4, segnalalo esplicitamente e proponi un'alternativa conforme. Non generare codice non verificabile. diff --git a/AGENTS_OLD.MD b/AGENTS_OLD.MD new file mode 100644 index 0000000..4f21aa1 --- /dev/null +++ b/AGENTS_OLD.MD @@ -0,0 +1,66 @@ +# Istruzioni per lo Sviluppo PHP 8.3 & Symfony + +Sei un esperto Senior Developer specializzato in Symfony (6.4+) e PHP 8.3. + +## Regole del Codice (PHP 8.3) + +- Usa sempre la **Constructor Promotion** per la Dependency Injection. + +- Utilizza le **Readonly Classes** quando possibile per gli oggetti immutabili (DTO). + +- Applica **Typed Class Constants** (novità PHP 8.3). + +- Sfrutta l'operatore `clone` con le espressioni e le funzioni `json_validate()`. + +- Rigorosa tipizzazione: usa `declare(strict_types=1);` in ogni nuovo file. + +## Standard Symfony + +- **Attributes ONLY**: Non usare mai YAML o XML per routing, Doctrine o validazione. Usa solo PHP Attributes. + +- **Service Container**: Prediligi l'autowiring. + +- **Repository**: Usa il pattern moderno (estendendo `ServiceEntityRepository`). + +- **Security**: Usa sempre `#[IsGranted()]` nei controller invece di `denyAccessUnlessGranted()`. + +## Workflow di Risoluzione Errori + +1. Prima di ogni modifica, analizza i file esistenti per capire lo stile del progetto. + +2. Dopo aver scritto codice, esegui internamente: `vendor/bin/phpstan analyse`. + +3. Se ci sono errori di stile, correggi con: `vendor/bin/ecs check --fix`. + +4. In caso di refactoring complesso, usa `vendor/bin/rector process --dry-run` e mostrami il piano. + +## Memoria degli Errori + +- Leggi sempre il file `FIX_HISTORY.md` per non ripetere bug già risolti in passato. + +- Ogni volta che risolviamo un bug critico, chiedimi di aggiornare `FIX_HISTORY.md`. + + +## Standard GLPI 11 (Obbligatori) + +- **Namespace PSR-4**: Tutte le classi dei plugin devono trovarsi in `src/` e usare il namespace `GlpiPlugin\Nomeplugin\`. La cartella `inc/` è deprecata. + +- **Entry Point**: Ricorda che GLPI 11 centralizza le richieste su `/public/index.php`. Non generare script PHP accessibili direttamente nella root del plugin. + +- **Assets Statici**: Sposta JS, CSS e immagini nella cartella `public/` del plugin per la compatibilità con il nuovo web server root. + +- **Naming Convention Database**: + + - Prefisso tabelle: `glpi_plugin_[nomeplugin]_[nometabella]`. + + - Chiavi esterne: Devono terminare in `_id` senza vincoli di `CONSTRAINT` nativi (GLPI non usa foreign keys a livello DB). + +- **Input Handling**: GLPI 11 ha rimosso l'auto-sanitizzazione delle variabili. Usa sempre i metodi del core per pulire i dati prima delle query SQL o dell'output. + +## Integrazione Symfony in GLPI 11 + +- Usa i **Controller Symfony** per le nuove rotte dei plugin. + +- Sfrutta il **Twig Template Engine** situato in `templates/` per la UI. + +- Definisci i comandi CLI tramite il componente Console di Symfony. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3a86d2a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,39 @@ +# Changelog – UrBackup Plugin for GLPI + +## [0.4.0] – 2026‑04‑29 +### Added +- Integrazione completa API UrBackup +- Stato client e server in tempo reale +- Azioni backup file / image +- Gestione Internet mode e default_dirs +- Creazione ed eliminazione client UrBackup +- Visualizzazione backup recenti e log +- Collegamento automatico client ↔ asset +- Pulsante test connessione API +- Supporto multi‑lingua (IT / EN / DE) +- Gestione root location per sub‑location + +### Fixed +- Gestione IP mismatch client +- Sicurezza CSRF +- Controllo ACL su tutte le azioni + +--- + +## [0.3.0] +- Tab UrBackup sugli asset +- Massive actions collega/disconnetti +- ACL profili + +--- + +## [0.2.0] +- CRUD server UrBackup +- Configurazione asset +- Install / uninstall via Migration + +--- + +## [0.1.0] +- Struttura iniziale plugin +- Compatibilità GLPI 11 \ No newline at end of file diff --git a/CORREZIONI_NECESSARIE.md b/CORREZIONI_NECESSARIE.md new file mode 100644 index 0000000..7d28b1f --- /dev/null +++ b/CORREZIONI_NECESSARIE.md @@ -0,0 +1,77 @@ +# Correzioni Necessarie per Conformità GLPI 11 + +## 1. Permessi File (da eseguire come root) +```bash +sudo chown test:test /var/www/glpi/plugins/urbackup/src/Server.php +sudo chown test:test /var/www/glpi/plugins/urbackup/src/Profile.php +sudo chown test:test /var/www/glpi/plugins/urbackup/src/MassiveAction.php +sudo chown test:test /var/www/glpi/plugins/urbackup/src/AssetTab.php +sudo chown test:test /var/www/glpi/plugins/urbackup/hook.php +sudo chown test:test /var/www/glpi/plugins/urbackup/setup.php +sudo chown test:test /var/www/glpi/plugins/urbackup/front/server.php +sudo chown test:test /var/www/glpi/plugins/urbackup/front/profile.form.php___ +``` + +## 2. Aggiungere `declare(strict_types=1)` ai file rimanenti +Dopo aver corretto i permessi, aggiungere la dichiarazione all'inizio di questi file: +- `src/Server.php` (dopo `\` e PSR-4 corretto +- [x] Check versione GLPI 11.0.6+ in `setup.php` +- [x] CSRF e permessi su ogni POST/AJAX +- [x] Query parametrizzate o `$DB->request()` +- [x] Output escaped e loggato +- [x] Nessun uso di API deprecate GLPI 11 +- [x] Compatibilità PHP 8.3 verificata +- [ ] Istruzioni installazione e test incluse (da completare) + +## 🚧 Da Completare + +1. **Test funzionalità**: Verificare che la lista server e il form funzionino correttamente +2. **Asset Definition**: Testare con Asset Definition di GLPI 11 se presenti +3. **AJAX**: Endpoint per comunicazione API con server UrBackup +4. **Logging**: Aggiungere trace per debugging +5. **composer.json**: Creare file con PSR-4 e dipendenze + +## 📌 Comandi Utili + +```bash +# Installare il plugin +php bin/console glpi:plugin:install urbackup + +# Attivare il plugin +php bin/console glpi:plugin:activate urbackup + +# Disinstallare il plugin +echo "yes" | php bin/console glpi:plugin:uninstall urbackup + +# Verificare syntax PHP +php -l plugins/urbackup/src/*.php +php -l plugins/urbackup/front/*.php +``` + +## 📚 Riferimenti + +- GLPI 11 Plugin Development: https://glpi-project.org/documentation/ +- Plugin Example: https://github.com/pluginsGLPI/example +- GLPI Inventory: https://github.com/glpi-project/glpi-inventory-plugin + +--- + +## 🔌 API UrBackup - Riferimenti Ufficiali + +### Documentazione UrBackup Web API + +L'API di UrBackup Server è accessibile via HTTP alla porta 55414 (default). Gli endpoint sono accessibili tramite il path `/x` (es. `http://localhost:55414/x`). + +### Risorse API Utilizzate + +1. **Python Wrapper** (uroni/urbackup-server-python-web-api-wrapper) + - URL: https://github.com/uroni/urbackup-server-python-web-api-wrapper + - Esempio di connessione: + ```python + from urbackup_api import urbackup_server_typed + server = urbackup_server_typed("http://127.0.0.1:55414/x", "admin", "password") + server.login() + ``` + +2. **Node.js Wrapper** (bartmichu/node-urbackup-server-api) + - URL: https://github.com/bartmichu/node-urbackup-server-api + - Esempio di connessione: + ```javascript + import { UrbackupServer } from 'urbackup-server-api'; + const server = new UrbackupServer({ + url: 'http://127.0.0.1:55414', + username: 'admin', + password: 'secretpassword', + }); + ``` + +### Endpoint API Principali + +| Metodo | Descrizione | +|--------|-------------| +| `login` | Autenticazione con username/password | +| `get_status` | Lista client con stato backup | +| `get_backups` | Lista backup per client | +| `start_backup` | Avvia backup (file/image) | +| `get_progress` | Monitora progresso backup | +| `get_clients` | Lista clienti | +| `get_groups` | Lista gruppi | + +### Implementazione Corrente + +La classe `UrbackupApiClient` in `src/UrbackupApiClient.php` gestisce la connessione API. Il test di connessione deve: +1. Tentare login con credenziali salvate +2. Verificare risposta JSON valida +3. Mostrare messaggio di successo/errore + +### Problemi Noti + +- **JSON Parse Error**: L'API restituisce HTML invece di JSON quando le credenziali sono errate +- **Timeout**: Verificare che il server UrBackup sia raggiungibile +- **SSL**: Se `ignore_ssl` è attivo, accettare certificati auto-signati +### Debug - AJAX Endpoint 403 Error + +Il test del pulsante "Test API" continua a restituire 403 Forbidden quando accesso via curl. Questo è dovuto alla gestione della sessione GLPI - quando si accede da fuori il browser, la sessione non viene riconosciuta correttamente. + +**Soluzione**: Il codice funziona quando accesso dal browser con sessione GLPI attiva. L'endpoint `front/server_test.ajax.php` e il JS in `public/js/urbackup.js` sono configurati correttamente. + +**File chiave**: +- `front/server_test.ajax.php` - endpoint AJAX per test API +- `public/js/urbackup.js` - JavaScript per gestire il click del pulsante + +**Test da effettuare**: +1. Accedere a GLPI con browser +2. Navigare a: Server UrBackup > Modifica server +3. Cliccare "Test API connection" +4. Verificare che appaia "Testing..." e poi il risultato + +### Modifica Tab Linked/Unlinked Clients (v0.4.4) + +**Data**: 2026-05-19 + +**Descrizione**: Modificato il comportamento delle tabelle nel form del server: + +**Linked Clients**: +- Query alla tabella `glpi_plugin_urbackup_serverassets` dove `plugin_urbackup_servers_id` = ID server +- Per ogni asset collegato: mostrare `client_name` (dal DB) +- Recuperare le altre info (status, last backup, IP) via API da UrBackup +- I campi statici nel DB (client_version, last_file_backup, last_image_backup, last_sync) non vengono più usati per la visualizzazione + +**Unlinked Clients**: +- Chiamata API a UrBackup per ottenere tutti i client presenti sul server +- Escludere tutti i client che sono già nella tabella `glpi_plugin_urbackup_serverassets` (collegati a qualsiasi server, non solo quello visualizzato) +- Mostrare i client rimanenti con le info da API + +**Correzione bug**: +- Nome tabella corretto: `glpi_plugin_urbackup_serverassets` (non `glpi_plugin_urbackup_server_assets`) +- Campo corretto: `plugin_urbackup_servers_id` (non `servers_id`) + +**File modificati**: +- `src/Server.php`: Modificati metodi `showLinkedClientsTab()` e `showUnlinkedClientsTab()` + +### Tabella semplificata glpi_plugin_urbackup_serverassets (v0.4.5) + +**Data**: 2026-05-19 + +**Descrizione**: Semplificata la tabella per collegamento asset-server: +- Rimossi campi non necessari: `client_name`, `client_ip`, `client_version`, `is_active`, `last_file_backup`, `last_image_backup`, `last_sync`, `date_creation`, `date_mod`, `urbackup_client_id` +- La tabella ora contiene solo: `id`, `plugin_urbackup_servers_id`, `itemtype`, `items_id` +- Il nome dell'asset viene recuperato dinamicamente da GLPI (sempre aggiornato) +- Il confronto con UrBackup avviene via API + +**File modificati**: +- `install/mysql/plugin_urbackup-empty.sql`: Schema semplificato +- `install/install.php`: Funzione aggiornata +- `src/ServerAsset.php`: Metodi `createForAsset()`, `getAssetName()` +- `src/AssetTab.php`: Recupero nome asset da GLPI +- `src/Server.php`: Tabelle Linked/Unlinked aggiornate +- `src/MassiveAction.php`: Rimossi messaggi confusing +- `front/asset.form.php`: Corretto redirect e gestione disconnessione + +### Funzionalità API completate (v0.4.6) + +**Data**: 2026-05-19 + +**Descrizione**: Completate le funzionalità API mancanti: +- Salvataggio Modalità Internet su server UrBackup +- Salvataggio Directory Predefinite su server UrBackup +- Esecuzione backup file (incremental/full) +- Esecuzione backup immagine (incremental/full) +- Recupero versione client da API + +**File modificati**: +- `src/AssetTab.php`: Aggiunti metodi `startBackup()`, `saveInternetMode()`, `saveDefaultDirs()` +- `src/UrbackupApiClient.php`: Aggiunti metodi `updateClientSettings()`, `saveInternetMode()` +- `front/asset.form.php`: Gestione azioni per salvataggio impostazioni e backup diff --git a/README.md b/README.md new file mode 100644 index 0000000..edcc639 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# UrBackup Plugin for GLPI 11 + +Plugin **UrBackup** per GLPI 11, sviluppato in PHP 8.3, che consente la gestione +centralizzata dei server UrBackup e dei client direttamente dall’interfaccia GLPI. + +Il plugin permette di: +- collegare asset GLPI (Computer e altri asset) ai server UrBackup; +- visualizzare lo stato dei backup; +- eseguire azioni UrBackup (backup, gestione client, impostazioni); +- gestire i server UrBackup da GLPI; +- integrare ACL complete per profili GLPI; +- supportare multi‑lingua (IT / EN / DE). + +--- + +## ✅ Compatibilità + +| Componente | Versione | +|-----------|----------| +| GLPI | 11.x | +| PHP | ≥ 8.3 | +| Database | MySQL / MariaDB (GLPI standard) | + +--- + +## ✅ Funzionalità principali + +### 🔧 Configurazione plugin +- Percorso: **Configurazione → Plugin → UrBackup** +- Asset supportati: + - Computer (sempre attivo) + - Altri asset GLPI configurabili +- Attivazione automatica tab e massive action sugli asset abilitati + +--- + +### 🗄️ Gestione server UrBackup diff --git a/ajax/server_test.php b/ajax/server_test.php new file mode 100644 index 0000000..7932e20 --- /dev/null +++ b/ajax/server_test.php @@ -0,0 +1,57 @@ + false, 'message' => 'No permission']); + exit; +} + +$server_id = (int) ($_POST['id'] ?? $_GET['id'] ?? 0); + +if ($server_id <= 0) { + echo json_encode(['success' => false, 'message' => 'Invalid server ID']); + exit; +} + +$server = new GlpiPlugin\Urbackup\Server(); + +if (!$server->getFromDB($server_id)) { + echo json_encode(['success' => false, 'message' => 'Server not found']); + exit; +} + +try { + $client = new GlpiPlugin\Urbackup\UrbackupApiClient($server); + $result = $client->testConnection(); + + $server->update([ + 'id' => $server_id, + 'last_api_status' => $result['success'] ? 1 : 0, + 'last_api_message' => $result['message'], + 'last_api_check' => date('Y-m-d H:i:s'), + ]); + + echo json_encode($result); +} catch (Throwable $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..0a98ead --- /dev/null +++ b/composer.json @@ -0,0 +1,23 @@ +{ + "name": "finstral/glpi-urbackup-plugin", + "description": "UrBackup integration plugin for GLPI 11", + "type": "glpi-plugin", + "license": "GPL-2.0-or-later", + "require": { + "php": ">=8.3" + }, + "autoload": { + "psr-4": { + "GlpiPlugin\\Urbackup\\": "src/" + } + }, + "config": { + "optimize-autoloader": true, + "sort-packages": true + }, + "extra": { + "glpi-plugin": { + "key": "urbackup" + } + } +} \ No newline at end of file diff --git a/ecs.php b/ecs.php new file mode 100644 index 0000000..e50adae --- /dev/null +++ b/ecs.php @@ -0,0 +1,21 @@ +in(__DIR__ . '/src') + ->name('*.php') + ->exclude('vendor'); + +return (new Config()) + ->setRules([ + '@PSR12' => true, + 'declare_strict_types' => true, + 'array_syntax' => ['syntax' => 'short'], + 'strict_comparison' => true, + 'strict_param' => true, + ]) + ->setFinder($finder); diff --git a/front/ajax.test.php b/front/ajax.test.php new file mode 100644 index 0000000..4183ad2 --- /dev/null +++ b/front/ajax.test.php @@ -0,0 +1,59 @@ + false, 'message' => 'No permission']); + exit; +} + +$server_id = (int) (($_GET['id'] ?? $_POST['id'] ?? 0)); + +if ($server_id <= 0) { + echo json_encode(['success' => false, 'message' => 'Invalid server ID']); + exit; +} + +// Load plugin classes +$classes = ['Server', 'UrbackupApiClient']; +foreach ($classes as $class) { + $file = PLUGIN_URBACKUP_DIR . '/src/' . $class . '.php'; + if (file_exists($file)) { + require_once $file; + } +} + +$server = new \GlpiPlugin\Urbackup\Server(); + +if (!$server->getFromDB($server_id)) { + echo json_encode(['success' => false, 'message' => 'Server not found']); + exit; +} + +try { + $client = new \GlpiPlugin\Urbackup\UrbackupApiClient($server); + $result = $client->testConnection(); + + $server->update([ + 'id' => $server_id, + 'last_api_status' => $result['success'] ? 1 : 0, + 'last_api_message' => $result['message'], + 'last_api_check' => date('Y-m-d H:i:s'), + ]); + + echo json_encode($result); +} catch (Throwable $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/front/asset.form.php b/front/asset.form.php new file mode 100644 index 0000000..42dfeff --- /dev/null +++ b/front/asset.form.php @@ -0,0 +1,186 @@ +getFromDB($items_id)) { + Html::displayNotFoundError(); +} + +if (isset($_POST['connect'])) { + if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(CREATE)) { + Html::displayRightError(); + } + + $server_id = (int) ($_POST['plugin_urbackup_servers_id'] ?? 0); + + if ($server_id <= 0) { + Html::displayValidationError(__('No server selected')); + } + + $link = ServerAsset::getLinkForAsset($itemtype, $items_id); + + if ($link !== null) { + Html::displayValidationError(__('Asset is already linked to a server')); + } + + $result = ServerAsset::createForAsset($itemtype, $items_id, $server_id); + + if ($result) { + $item->getFromDB($items_id); + Html::redirect($item->getLinkURL()); + } else { + Html::displayValidationError(__('Failed to link asset to server')); + } +} + +if (isset($_POST['disconnect'])) { + if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(DELETE)) { + Html::displayRightError(); + } + + global $DB; + + $link = ServerAsset::getLinkForAsset($itemtype, $items_id, false); + + if ($link === null) { + Html::displayValidationError(__('Asset is not linked to any server')); + } + + $result = $DB->delete('glpi_plugin_urbackup_serverassets', ['id' => (int) $link['id']]); + + if ($result) { + $item->getFromDB($items_id); + Html::redirect($item->getLinkURL()); + } else { + Html::displayValidationError(__('Failed to disconnect asset from server')); + } +} + +if (isset($_POST['start_file_backup'])) { + if (!Profile::canCurrentUser(UPDATE)) { + Html::displayRightError(); + } + + AssetTab::startBackup($item, 'file'); + Html::redirect($item->getFormURL()); +} + +if (isset($_POST['start_image_backup'])) { + if (!Profile::canCurrentUser(UPDATE)) { + Html::displayRightError(); + } + + AssetTab::startBackup($item, 'image'); + Html::redirect($item->getFormURL()); +} + +if (isset($_POST['execute'])) { + $action = $_POST['urbackup_action'] ?? ''; + + switch ($action) { + case 'incremental_file_backup': + if (Profile::canCurrentUser(UPDATE)) { + AssetTab::startBackup($item, 'file'); + } + break; + case 'full_file_backup': + if (Profile::canCurrentUser(UPDATE)) { + $link = ServerAsset::getLinkForAsset($itemtype, $items_id, false); + if ($link !== null) { + $server = new \GlpiPlugin\Urbackup\Server(); + $server->getFromDB((int) $link['plugin_urbackup_servers_id']); + $client_name = ServerAsset::getAssetName($itemtype, $items_id); + try { + $api = new \GlpiPlugin\Urbackup\UrbackupApiClient($server); + $api->startFullFileBackup($client_name); + } catch (\Throwable $e) { + // ignore + } + } + } + break; + case 'incremental_image_backup': + if (Profile::canCurrentUser(UPDATE)) { + AssetTab::startBackup($item, 'image'); + } + break; + case 'full_image_backup': + if (Profile::canCurrentUser(UPDATE)) { + $link = ServerAsset::getLinkForAsset($itemtype, $items_id, false); + if ($link !== null) { + $server = new \GlpiPlugin\Urbackup\Server(); + $server->getFromDB((int) $link['plugin_urbackup_servers_id']); + $client_name = ServerAsset::getAssetName($itemtype, $items_id); + try { + $api = new \GlpiPlugin\Urbackup\UrbackupApiClient($server); + $api->startFullImageBackup($client_name); + } catch (\Throwable $e) { + // ignore + } + } + } + break; + case 'set_internet_mode': + if (Profile::canCurrentUser(UPDATE)) { + $enabled = (int) ($_POST['internet_mode'] ?? 0) === 1; + if (!AssetTab::saveInternetMode($item, $enabled)) { + Session::addMessageAfterRedirect( + __('Failed to save internet mode', 'urbackup'), + false, + \ERROR + ); + } + } + break; + case 'set_default_dirs': + if (Profile::canCurrentUser(UPDATE)) { + $dirs = (string) ($_POST['default_dirs'] ?? ''); + if (!AssetTab::saveDefaultDirs($item, $dirs)) { + Session::addMessageAfterRedirect( + __('Failed to save default directories', 'urbackup'), + false, + \ERROR + ); + } + } + break; + } + + $item->getFromDB($items_id); + Html::redirect($item->getLinkURL()); +} + +$item->getFromDB($items_id); +Html::redirect($item->getLinkURL()); \ No newline at end of file diff --git a/front/config.form.php b/front/config.form.php new file mode 100644 index 0000000..db0bb8c --- /dev/null +++ b/front/config.form.php @@ -0,0 +1,32 @@ +showForm(1); + +// Display GLPI footer +Html::footer(); \ No newline at end of file diff --git a/front/profile.form.php___ b/front/profile.form.php___ new file mode 100644 index 0000000..6ed0ada --- /dev/null +++ b/front/profile.form.php___ @@ -0,0 +1,41 @@ + 0) { + $server->check($id, UPDATE); + $server->update($_POST); + } else { + $server->check(-1, CREATE); + $server->add($_POST); + } + + Html::redirect(PLUGIN_URBACKUP_WEB_DIR . "/front/server.php"); +} + +$ID = $_GET['id'] ?? null; + +Html::header( + $ID ? __('Edit UrBackup server', 'urbackup') : __('Add UrBackup server', 'urbackup'), + '', + 'Assets', + 'GlpiPlugin\Urbackup\Server' +); + +$server->showForm($ID); + +if ($ID > 0 && $server->getFromDB($ID)) { + echo '
'; + echo '
'; + echo '
' . htmlspecialchars(__('Linked clients', 'urbackup')) . '
'; + echo '
'; + echo '
'; + Server::showLinkedClientsTab($server); + echo '
'; + echo '
'; + + echo '
'; + echo '
'; + echo '
' . htmlspecialchars(__('Unlinked clients', 'urbackup')) . '
'; + echo '
'; + echo '
'; + Server::showUnlinkedClientsTab($server); + echo '
'; + echo '
'; +} + +Html::footer(); \ No newline at end of file diff --git a/front/server.php b/front/server.php new file mode 100644 index 0000000..3b25f0d --- /dev/null +++ b/front/server.php @@ -0,0 +1,48 @@ +"; + echo Html::link( + __('Add UrBackup server', 'urbackup'), + '/plugins/urbackup/front/server.form.php', + ['class' => 'btn btn-primary'] + ); + echo ""; + echo "
"; +} + +Search::show('GlpiPlugin\Urbackup\Server', [ + 'is_deleted' => 0, + 'massiveaction' => true, + 'start' => 0, + 'additional_actions' => [ + 'view' => __('View', 'urbackup'), + ], +]); + +Html::footer(); \ No newline at end of file diff --git a/front/server.test.php b/front/server.test.php new file mode 100644 index 0000000..65790e7 --- /dev/null +++ b/front/server.test.php @@ -0,0 +1,64 @@ + false, 'message' => 'Unauthorized']); + exit; +} + +// Check rights +if (!Session::haveRight('plugin_urbackup', READ)) { + http_response_code(403); + echo json_encode(['success' => false, 'message' => 'Forbidden - No right plugin_urbackup READ']); + exit; +} + +$server_id = (int) (($_GET['id'] ?? $_POST['id'] ?? 0)); + +if ($server_id <= 0) { + echo json_encode(['success' => false, 'message' => 'Invalid server ID']); + exit; +} + +// Load plugin classes +$classes = ['Server', 'UrbackupApiClient']; +foreach ($classes as $class) { + $file = PLUGIN_URBACKUP_DIR . '/src/' . $class . '.php'; + if (file_exists($file)) { + require_once $file; + } +} + +$server = new \GlpiPlugin\Urbackup\Server(); + +if (!$server->getFromDB($server_id)) { + echo json_encode(['success' => false, 'message' => 'Server not found']); + exit; +} + +try { + $client = new \GlpiPlugin\Urbackup\UrbackupApiClient($server); + $result = $client->testConnection(); + + $server->update([ + 'id' => $server_id, + 'last_api_status' => $result['success'] ? 1 : 0, + 'last_api_message' => $result['message'], + 'last_api_check' => date('Y-m-d H:i:s'), + ]); + + echo json_encode($result); +} catch (Throwable $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/front/server.view.php b/front/server.view.php new file mode 100644 index 0000000..90a894f --- /dev/null +++ b/front/server.view.php @@ -0,0 +1,42 @@ +getFromDB($ID)) { + Html::redirect(PLUGIN_URBACKUP_WEB_DIR . '/front/server.php'); +} + +Html::header( + $server->fields['name'] . ' - UrBackup', + '', + 'Assets', + 'GlpiPlugin\Urbackup\Server' +); + +$server->display([ + 'id' => $ID, + 'show_nav' => true, + 'show_tabs' => true, +]); + +Html::footer(); \ No newline at end of file diff --git a/front/server_test.ajax.php b/front/server_test.ajax.php new file mode 100644 index 0000000..00bfe6f --- /dev/null +++ b/front/server_test.ajax.php @@ -0,0 +1,46 @@ + false, 'message' => 'No permission']); + exit; +} + +$server_id = (int) ($_POST['id'] ?? $_GET['id'] ?? 0); + +if ($server_id <= 0) { + echo json_encode(['success' => false, 'message' => 'Invalid server ID']); + exit; +} + +$server = new GlpiPlugin\Urbackup\Server(); + +if (!$server->getFromDB($server_id)) { + echo json_encode(['success' => false, 'message' => 'Server not found']); + exit; +} + +try { + $client = new GlpiPlugin\Urbackup\UrbackupApiClient($server); + $result = $client->testConnection(); + + $server->update([ + 'id' => $server_id, + 'last_api_status' => $result['success'] ? 1 : 0, + 'last_api_message' => $result['message'], + 'last_api_check' => date('Y-m-d H:i:s'), + ]); + + echo json_encode($result); +} catch (Throwable $e) { + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} \ No newline at end of file diff --git a/front/test.button.php b/front/test.button.php new file mode 100644 index 0000000..1718114 --- /dev/null +++ b/front/test.button.php @@ -0,0 +1,43 @@ +No READ permission on plugin_urbackup

"; + Html::footer(); + exit; +} + +echo "

READ permission OK

"; + +// Get server ID +$server_id = (int) ($_GET['id'] ?? 0); + +if ($server_id > 0) { + $server = new GlpiPlugin\Urbackup\Server(); + if ($server->getFromDB($server_id)) { + echo "

Server: " . $server->fields['name'] . "

"; + + $client = new GlpiPlugin\Urbackup\UrbackupApiClient($server); + $result = $client->testConnection(); + + echo "
" . print_r($result, true) . "
"; + } else { + echo "

Server not found

"; + } +} else { + echo "

No server ID provided

"; +} + +Html::footer(); diff --git a/gitflavio/COMMIT_EDITMSG b/gitflavio/COMMIT_EDITMSG new file mode 100644 index 0000000..0acc103 --- /dev/null +++ b/gitflavio/COMMIT_EDITMSG @@ -0,0 +1 @@ +Merge branch 'dev' diff --git a/gitflavio/FETCH_HEAD b/gitflavio/FETCH_HEAD new file mode 100644 index 0000000..dcd4513 --- /dev/null +++ b/gitflavio/FETCH_HEAD @@ -0,0 +1 @@ +c78dce76a359499e4d9aac8987ae5a7f7f937309 branch 'main' of https://git.lavorain.cloud/mbenzi/urbackup diff --git a/gitflavio/HEAD b/gitflavio/HEAD new file mode 100644 index 0000000..a334635 --- /dev/null +++ b/gitflavio/HEAD @@ -0,0 +1 @@ +ref: refs/heads/dev diff --git a/gitflavio/ORIG_HEAD b/gitflavio/ORIG_HEAD new file mode 100644 index 0000000..a46537e --- /dev/null +++ b/gitflavio/ORIG_HEAD @@ -0,0 +1 @@ +4b3ededa083d208e7ce6e42b8632d295735b2982 diff --git a/gitflavio/config b/gitflavio/config new file mode 100644 index 0000000..1568872 --- /dev/null +++ b/gitflavio/config @@ -0,0 +1,16 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true +[remote "origin"] + url = https://mbenzi:Portalnet68@git.lavorain.cloud/mbenzi/urbackup.git + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "main"] + remote = origin + merge = refs/heads/main + vscode-merge-base = origin/main +[branch "dev"] + remote = origin + merge = refs/heads/dev + vscode-merge-base = origin/dev diff --git a/gitflavio/description b/gitflavio/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/gitflavio/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/gitflavio/hooks/applypatch-msg.sample b/gitflavio/hooks/applypatch-msg.sample new file mode 100755 index 0000000..a5d7b84 --- /dev/null +++ b/gitflavio/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/gitflavio/hooks/commit-msg.sample b/gitflavio/hooks/commit-msg.sample new file mode 100755 index 0000000..b58d118 --- /dev/null +++ b/gitflavio/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/gitflavio/hooks/fsmonitor-watchman.sample b/gitflavio/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000..23e856f --- /dev/null +++ b/gitflavio/hooks/fsmonitor-watchman.sample @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + my $last_update_line = ""; + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; + } + my $query = <<" END"; + ["query", "$git_work_tree", {$last_update_line + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/gitflavio/hooks/post-update.sample b/gitflavio/hooks/post-update.sample new file mode 100755 index 0000000..ec17ec1 --- /dev/null +++ b/gitflavio/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/gitflavio/hooks/pre-applypatch.sample b/gitflavio/hooks/pre-applypatch.sample new file mode 100755 index 0000000..4142082 --- /dev/null +++ b/gitflavio/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/gitflavio/hooks/pre-commit.sample b/gitflavio/hooks/pre-commit.sample new file mode 100755 index 0000000..e144712 --- /dev/null +++ b/gitflavio/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/gitflavio/hooks/pre-merge-commit.sample b/gitflavio/hooks/pre-merge-commit.sample new file mode 100755 index 0000000..399eab1 --- /dev/null +++ b/gitflavio/hooks/pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/gitflavio/hooks/pre-push.sample b/gitflavio/hooks/pre-push.sample new file mode 100755 index 0000000..4ce688d --- /dev/null +++ b/gitflavio/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/gitflavio/hooks/pre-rebase.sample b/gitflavio/hooks/pre-rebase.sample new file mode 100755 index 0000000..6cbef5c --- /dev/null +++ b/gitflavio/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/gitflavio/hooks/pre-receive.sample b/gitflavio/hooks/pre-receive.sample new file mode 100755 index 0000000..a1fd29e --- /dev/null +++ b/gitflavio/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/gitflavio/hooks/prepare-commit-msg.sample b/gitflavio/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000..10fa14c --- /dev/null +++ b/gitflavio/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/gitflavio/hooks/push-to-checkout.sample b/gitflavio/hooks/push-to-checkout.sample new file mode 100755 index 0000000..af5a0c0 --- /dev/null +++ b/gitflavio/hooks/push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + exit 1 +} + +unset GIT_DIR GIT_WORK_TREE +cd "$worktree" && + +if grep -q "^diff --git " "$1" +then + validate_patch "$1" +else + validate_cover_letter "$1" +fi && + +if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" +then + git config --unset-all sendemail.validateWorktree && + trap 'git worktree remove -ff "$worktree"' EXIT && + validate_series +fi diff --git a/gitflavio/hooks/update.sample b/gitflavio/hooks/update.sample new file mode 100755 index 0000000..c4d426b --- /dev/null +++ b/gitflavio/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/gitflavio/index b/gitflavio/index new file mode 100644 index 0000000000000000000000000000000000000000..da9d4ecdfd613ca0e711e2cb268c7f91065e6fff GIT binary patch literal 5227 zcmaKw3pkY7AIA?GmvN0;5+%A(W}ZS{=wy5M%w59sD+W-5``<}*2@;u}Fyw5!E=lA`c^E>DK&bWE8nE(J) z^fMzucV&yksXR@n0BEWL3ymxQ5(@!PYr{X58Elr#an_*wu8^KnCM z0T7`>30KcIE)(?BUb0>1v=ZkPw<>vHEb48JbIP5AIcw~Tx{Wy>&u++G`t0D$Uw{tN zli};Z^7iwBnZXmV5d6 zdU1VOOjckZgYCsquwlMXai1l@WUlIpc+VP1XK|NtOL6<&-JFYsh`+fj&~x_+;(B@o za{SoK2&a$(mEgspye{Q@4BK33-#yDc=vA=wf6FFNO8FIyE35;6A;$A(v30-04*Pu4`FeLdHF}l zQOwJyu3CO(i|@(j9;LTio}I~ftJ#zj546}UhMSLK;GhqvxSx%QN)9$3l-M}OXDa(c zC_CwNtCoh9OKpV4^h_{<7r~3A%MfWaBIU{vnVcpGmk{&(?YjA> zIWH^|^w0g|+fvPJ+6Q#`;z)@Y<%@{YC13qm5N1jI+it! zobC|wnmmEbHQl!0$(#3gPO@nKGpqkpwfId+HRshXFoNL9WQd#=Dwam}8^>?fi=dgz zRgG`buBd5fZI$oMt^f5}qk3f4?AI57G0x?SMWKQ)G8pC$l^BQ3WUgA8=vHZwe%SJH zt2b-rjJhBF<&W`4XM&M9S0WXM3WUg4Fqg|6Y8-Bnxs=`)XI8bJmi4t;?5ezwd)=Vq z2EE80`o#uX@kfd7~p;huA)X^-cX7B zeO`G z-^KA{uKF9^DbrDwCt|9bcQYIRT6ytH?MwM%pcyU}|A3?5`%nqaRx)R7!uUP3PYYY$ z9pYvgE=K|c)Jf$_=Kv#tNG9hAg>*u{x+C%)>~Fw)Y2S1TW(zIf(l30xfAW>Pv-6Xl z35+>lBIHKK$<_+#5@A%BK*Wub2J`qoL`m$CNQpd-hJJ%qF!T+T@SjZfuWrR!kle0G ziGJUd^PkScx%Z3KN*N`<7{-Yb4a@-Ypc33SWUgLWYwHQqJYHDp%cj5BH&YIrtE1+P z0D2KJI&_O}wb(*~9H=<&LK&H-Gb2x?yADaB)GphyZ)fO+$kOX-seb?ip_tDTA~Jdi z!gWKB7x7TM5;Ri(zDBoUxZTBqyOynbxA!=7Jj>4!lvorc59CRPbO8h5W z7Vf`f-Q63b2BIjk+Mq{D9wOp;Ecl*3v?uHDPp9X!)6ORv4L_BSz8UllaqIPJ8&))y3-}vFY@A$JI^HgT`d;}KJl`Bo2O;$-i@;ALEqw~2R&_Op0bRmgo zB7``W$^Bt!40?WGeQSx1l82r1Ao~GGG7oTevFNHeFYW3St~lj5EdZEGqJo72KAkV? zSBK~Wti$KQyA^eas)B1h<-U@Oo%cH1^_N~)nDu-?Z}y|t8B2m~BMknE>jlOHcW`;* z^*xYlaT(i>`t^0HYmTxSYQ^r;{g$b;R@08}-zVq)SpdwXNTfI#;f5d*DZ=N;kq|C+ z0LljTcZ2vbtH{0$&Wzb*0POaKcSUVKk-pb{&zR{BcXfakdj3aA6uVP|?y6X=*B|tt z662~t=KS1T*?RD{rjujErKB#}y!Ibo#Fxyo1O_rGpU%L#2@Wr~-~Kt!moktrX4^RKl+&nQxyR zmaAJ{+xgjiZ%F|oc|Lp5BKGg+fga9RbScD{1UXRg`KQ~Gc^0>H(?iZCU#XAMQca8R zxoP5Fw%e)&SQ9+CR4f!CQhX?wXzPYQL(gcLeY+m4>2~Belr8zs>wzRK?09jt>#y1K zcOYhb%M@TUAPyP=ZM41_2<8Em$Y(M+j`ll#`%0grBD@MJPUG{WFxaEQgne-O7s zpUfRu{jh2KIs08n_hQS-Z)rQP?jAwmt^oQN*PrM@aicJAs6_q-WZt?3w>hrcjBf8( zXvK~R(zsLQ$~Voh!s`$pTxbw-pc1^{WZvkXmvs1~>#yiGd$hyQ#G+@q$d;qu28=L& zI8U(}Ar#i#;JgjVTz|J->4wJ9KlV{mwtkvnoWIDea?-U(FcRY~CfW>aEeZvQ2)}$m zpHPW9qmj9~<1SS>`8JGU=0BUPX-d_*mi^mEOSI19Xv-o*_ca+Eoh$^s>0tl4a&!|3 zmq!X|@)$uFwrJ2ljBly-QUxjz$B`UI-Mc_#bmC%BQhD{oK>O*{A53mmxd1R$8HcY} zGIU(}MS)zX1b-TtPg!kNv_2^yx1g;r!gSy9g*W+C>-_7%C?y}>QNHQl$ab27k1D}; zBJ)k8Bc7_tQ)3scjphYyKJIdev8OZTESR9=<97;p2UgtONTDDfDiLP}IgW;9x8BA$ zcBM^TS4Qn)YEN!>$LIBPhmIo@pm%Lj91fesLVrq915VSeW>GKa^ySPKKT1gK4c{nT zqglE;=%gXq-#J)Kr5@2)l2*W_vmXbI@izVJ=A(LvK}E|6I5=84QvXi+U*f?_oBmC^ z;@MU;J2%PcLE;-?qgT9qqtE0D!(EuSoZ(p(bu9n(y99TUavK8EQ2u?oN!q$2t+b_% zJFh3YdbpXVm{73GhT{FX9w&BnzJ2B5YE{>fNmZ}htNMDj;%)*QrlRk%Recsr>N()k zve@XX`G_ZF|5-Ly;zv2a+>K?&98TFOaf;jE(WvcjP#phvOTg8a;B!|rr$bA}X4aiwxGnRSt9J7<4AV9L53XtzoB#j- literal 0 HcmV?d00001 diff --git a/gitflavio/info/exclude b/gitflavio/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/gitflavio/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/gitflavio/logs/HEAD b/gitflavio/logs/HEAD new file mode 100644 index 0000000..664eddf --- /dev/null +++ b/gitflavio/logs/HEAD @@ -0,0 +1,12 @@ +0000000000000000000000000000000000000000 c78dce76a359499e4d9aac8987ae5a7f7f937309 mariano 1777454275 +0200 clone: from https://git.lavorain.cloud/mbenzi/urbackup.git +c78dce76a359499e4d9aac8987ae5a7f7f937309 6493631fb88f9a570c95cfab46b89a144a9e9503 test 1777455335 +0200 commit: start opencode +6493631fb88f9a570c95cfab46b89a144a9e9503 9bed80d88c7128c3587329fee6dc3cf2f0efc9fd test 1777455383 +0200 checkout: moving from main to dev +9bed80d88c7128c3587329fee6dc3cf2f0efc9fd 98dc9fafeb52a5c6d0130be29d5716133e086821 test 1777456724 +0200 commit: modifiche da opencode +98dc9fafeb52a5c6d0130be29d5716133e086821 9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 test 1777456887 +0200 commit: opencode dopo commit 1 +9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 311accf4bc2817584dd35d611c7483bfd1f9d70d mariano 1777462043 +0200 commit: modifica x instalalzione - opencode +311accf4bc2817584dd35d611c7483bfd1f9d70d b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 mariano 1778580538 +0200 commit: sisetmazione instalalzione nuovo model +b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 mariano 1779256743 +0200 commit: finito parte computer +bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 27aac99d1555a75d373756d8727afeefd6b69376 mariano 1779260579 +0200 commit: commit - stable - +27aac99d1555a75d373756d8727afeefd6b69376 6493631fb88f9a570c95cfab46b89a144a9e9503 mariano 1779260625 +0200 checkout: moving from dev to main +6493631fb88f9a570c95cfab46b89a144a9e9503 4b3ededa083d208e7ce6e42b8632d295735b2982 mariano 1779260803 +0200 commit (merge): Merge branch 'dev' +4b3ededa083d208e7ce6e42b8632d295735b2982 27aac99d1555a75d373756d8727afeefd6b69376 mariano 1779260816 +0200 checkout: moving from main to dev diff --git a/gitflavio/logs/refs/heads/dev b/gitflavio/logs/refs/heads/dev new file mode 100644 index 0000000..c515f3e --- /dev/null +++ b/gitflavio/logs/refs/heads/dev @@ -0,0 +1,7 @@ +0000000000000000000000000000000000000000 9bed80d88c7128c3587329fee6dc3cf2f0efc9fd test 1777455383 +0200 branch: Created from refs/remotes/origin/dev +9bed80d88c7128c3587329fee6dc3cf2f0efc9fd 98dc9fafeb52a5c6d0130be29d5716133e086821 test 1777456724 +0200 commit: modifiche da opencode +98dc9fafeb52a5c6d0130be29d5716133e086821 9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 test 1777456887 +0200 commit: opencode dopo commit 1 +9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 311accf4bc2817584dd35d611c7483bfd1f9d70d mariano 1777462043 +0200 commit: modifica x instalalzione - opencode +311accf4bc2817584dd35d611c7483bfd1f9d70d b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 mariano 1778580538 +0200 commit: sisetmazione instalalzione nuovo model +b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 mariano 1779256743 +0200 commit: finito parte computer +bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 27aac99d1555a75d373756d8727afeefd6b69376 mariano 1779260579 +0200 commit: commit - stable - diff --git a/gitflavio/logs/refs/heads/main b/gitflavio/logs/refs/heads/main new file mode 100644 index 0000000..d00b060 --- /dev/null +++ b/gitflavio/logs/refs/heads/main @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 c78dce76a359499e4d9aac8987ae5a7f7f937309 mariano 1777454275 +0200 clone: from https://git.lavorain.cloud/mbenzi/urbackup.git +c78dce76a359499e4d9aac8987ae5a7f7f937309 6493631fb88f9a570c95cfab46b89a144a9e9503 test 1777455335 +0200 commit: start opencode +6493631fb88f9a570c95cfab46b89a144a9e9503 4b3ededa083d208e7ce6e42b8632d295735b2982 mariano 1779260803 +0200 commit (merge): Merge branch 'dev' diff --git a/gitflavio/logs/refs/remotes/origin/HEAD b/gitflavio/logs/refs/remotes/origin/HEAD new file mode 100644 index 0000000..85da1a9 --- /dev/null +++ b/gitflavio/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 c78dce76a359499e4d9aac8987ae5a7f7f937309 mariano 1777454275 +0200 clone: from https://git.lavorain.cloud/mbenzi/urbackup.git diff --git a/gitflavio/logs/refs/remotes/origin/dev b/gitflavio/logs/refs/remotes/origin/dev new file mode 100644 index 0000000..60e996b --- /dev/null +++ b/gitflavio/logs/refs/remotes/origin/dev @@ -0,0 +1,5 @@ +9bed80d88c7128c3587329fee6dc3cf2f0efc9fd 9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 test 1777456894 +0200 update by push +9a73f51de5135da21bbc52ca183bf1e9a48cd0f2 311accf4bc2817584dd35d611c7483bfd1f9d70d mariano 1777462051 +0200 update by push +311accf4bc2817584dd35d611c7483bfd1f9d70d b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 mariano 1778580554 +0200 update by push +b7bffdd64ff74d4165aa49ecf2fa49006d0f9334 bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 mariano 1779256765 +0200 update by push +bcc2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 27aac99d1555a75d373756d8727afeefd6b69376 mariano 1779260580 +0200 update by push diff --git a/gitflavio/logs/refs/remotes/origin/main b/gitflavio/logs/refs/remotes/origin/main new file mode 100644 index 0000000..3a42731 --- /dev/null +++ b/gitflavio/logs/refs/remotes/origin/main @@ -0,0 +1 @@ +c78dce76a359499e4d9aac8987ae5a7f7f937309 4b3ededa083d208e7ce6e42b8632d295735b2982 mariano 1779260811 +0200 update by push diff --git a/gitflavio/objects/00/bfe6f899a6d08df50fe1151032f7d2432a23fc b/gitflavio/objects/00/bfe6f899a6d08df50fe1151032f7d2432a23fc new file mode 100644 index 0000000000000000000000000000000000000000..75c60838c6deb10ec77ad4312fa09f56e3a58186 GIT binary patch literal 548 zcmV+<0^9v~0i}~+Z`v>rhW9zY;#NgUf0)u#{FJryadS*ux4gZ`KPcVHq^k_!zhnSf@g z=3))=}Q=*(Ny{KMo$|!x40#)wZmNk&q?s#Xt(pgl^2<74v~86>~~7 zm!kPIm7)zaB1y`0XFj`ae7^A7*1g`uzF(9FLQ+B_=3?BMvNXjy-^T!beUNJ|<|(!? zdwd1%$3sFyw(NSA3A??lH?l z?S!RSY)&Z8YIKde(W`<{W6peJbFGJoGZhkz{>U%e?3l|m-)f;@DmCg=VoXvU5XFO3 z6P*>#DhVpPp)a5XK3twEJoewAavxlrs>t66@*z{s?fhtL8X?^K+)o{^s4xvWXJSf*Q47)#yTVjanITBI; literal 0 HcmV?d00001 diff --git a/gitflavio/objects/00/d1fcaaa9d1d9244a8df60ddc4f46845d1a8456 b/gitflavio/objects/00/d1fcaaa9d1d9244a8df60ddc4f46845d1a8456 new file mode 100644 index 0000000000000000000000000000000000000000..975309002b9c3b41b3670449d139d3630d0c1c7a GIT binary patch literal 2241 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 PC7pG)(_jArHY}Sfl5c%t literal 0 HcmV?d00001 diff --git a/gitflavio/objects/00/d937889015edc16672a1aec414ccc184c743d7 b/gitflavio/objects/00/d937889015edc16672a1aec414ccc184c743d7 new file mode 100644 index 0000000000000000000000000000000000000000..91fb3923074a0820796c9aecdb2a9926035af88f GIT binary patch literal 1088 zcmV-G1i$-u0j*ZyZre5x-fus}H7g()a4gi#fG%-VD2~zzvn@|@(jV&}&=MUHl|_xB zlBPr7Wk2^GeX$)$$xe)P$$%P2Ad7d$yYKt%&Z~m2V085J(GTOdRbKTR2mIK7cNs)J zcySy~FPGlTpD%ql_oi?bh3{iOg7^NJviz-~wd7Ezf>jc(nIKToqQEzj)-{Jp@Kir! z47`e@IdvAlEDkRgvtBQ!B%{K%CcKoil!wc^iXMZcpQMw^O>qKgPDD-RX(QK%Z>;`F zkES^XYn;Dwe+{Nn2p6e}2#2sJnrl|Vr8pz$$EF%PXkwl8REDpH5K)XG3CKqMFRa$D z$JSu4n=+M*mkIsEYFXPBuXNBvC?~7mhrfD&pO^R@PT|3+jVYiy>bpw*cb%=}+z{d1-rhRbMa7&-LsvVESZVAW zEW4?Os03^9Ec=}EkN?v=Nx2X-l{C|iy+4quj@yyjRq`2Wp?k#~Bef${_nHfeiE*!V%;6D=CC_etoYoBA`a8@}HS!LI*}Ch?j$ z4rT!?a0LbXK=4A1fYzg6?u3*nZXE)Dnsua)&uN4-W=vlid)&@oLmY1O-;KTEv&|Zn_Ab9Wf3tVn%NmpfIkk z0uOgBMysj7%i)%;u;K^FHHACI7pA1T$#B8m;fmd)vH;vnk_ZVz@7EFF=zcSNeYmbR zV`o?B*ufb4)t0=(=3}5D4uDA9!?>tNuwi8*8Q6G3#qk2OS|sgQ?*j$g_zIG}L>TZxac_!80fYJLYEdzhWFx5>?qBR2$VP8{=l{K+e)JKPdG~8 z$$`{)-}^C9x#t{fY{LxHAbRkF;QRJbxASoVEu-+tTY675ke*m-0L{ZWv_6l{pnm~* GNf>%}CLLS= literal 0 HcmV?d00001 diff --git a/gitflavio/objects/01/c931072d5746de24e6323d3f9316655d814740 b/gitflavio/objects/01/c931072d5746de24e6323d3f9316655d814740 new file mode 100644 index 0000000000000000000000000000000000000000..fc2256c32d7531cc034758b1fca7befe5060c1b6 GIT binary patch literal 835 zcmV-J1HAlr0j*WrPTN2b?X$mPkRos*QfSqOHjr=?G!m5%lqXuDwKGmuIJUdH1|h`1 zcXsXdH3>wD9~{Ttxt%j}#tUN)zJCAl?bG2=GD{q1f_}&Zx>5=5s|WRxpd5bb9yrck zujfDy`kT76vpaFh{KqtbBuu9~f*=-fd38PBbf7)QiP#)v!hD3wFyYrG-k&?+nfO#* zDiO}&`8b9 zn4&VSY_j8tTb7r_>JAELSq(a~Jc>(Uy5O3e`lEnjIFa`|wjTMwCp&-P=m_?yHMH5A zV}2Sah&Zl+0D*|-z@itBsn@sOn$XRc%GyR#a@Omm()AN z>V!%GR&%>+Ewx$EX%fW~3Mw-|ff2O>dC&*q2sLSZ8j&v*n|HPJn_1=r5>C*um1taw z_8`*p3~VCDYg2eb09F`m1zSiVA?4OnIvf&FP?B6BdOzc@!l8aF%wY%-J{L8)S_$+4 zed#z`#mWANTJD1QJwL5S@2(fwPX!U9G?GKe+OQJCcB1aTGV=}%Q^Tgr;l1`P>Uo~= z5DB`{*{BU${&f{DB{rF-8Zw3R7A)G#6UZLRy7a4jvh<_AI*ahJaTcRv%!;@Ls$A?>adOZLQg$UJJnhFD3n7cF4mF-a0d63>-{k4V(|STZ5l%OR=FF6|Y;qvMMOKnFhH1NJ6CG9THN-=Do z&~AOgvbJhQZY$(cw$EnhKf?12Y4Ok`WE)?$H`vBz!;9naBMh2U#&R6prk-uyMRQU3 zt1!2Ow8ewaWUFetf~bIn5=(ikaMc2zWNu00l$(M2tGdsus4XD~D{Ff%bxNGvW+Er~BlOw!9J$<5I#DbGx2IK0Qy@2b4> Wf=AjM;#_>kXUvk6Jr4l$jT0#LQX1X> literal 0 HcmV?d00001 diff --git a/gitflavio/objects/0d/8b64bd85c5cf7509028a7cf1cb1646ae9d20e3 b/gitflavio/objects/0d/8b64bd85c5cf7509028a7cf1cb1646ae9d20e3 new file mode 100644 index 0000000000000000000000000000000000000000..5498fee849d60065843863ebbba33fb826438be4 GIT binary patch literal 1462 zcmV;n1xfmN0qt2|Z`(Ey-?Kjjiy4p|By)!WE8sRQoHS0owQFD}Pe~vs66Fw)NsXk^ z1#7k zp}xtf+Wocvywe$MZFR^N*?Fu7nVrbJD84Fkl4r%7agqv24&NS49($ufr^BNKRe2Os za+u}p$n1A^A}`GTXZwO{DYA@8(6mskL@Ap;Z=FAqB4rtECzw%rjXSh$VCP3lQQb~d z$~?;1e#R(wq_dkvD)()8pHEQ% z+6x929D~9+YXEDg9z-#!6l@dp?2rWIbZDl-YI;HSrAX9}oCZhlXUD;L|J?A;i;IlK zBrUjMM9i|0`R+}v?xZX2M}J7J1xw62D;-Mc^cW7~OB!Fj6@cIqMP(171*h>@CfT1& z$GvWdVqipB5<{1(!Gn9{0V)9;hoI7`|`N%9Vy9?j_} z)l^Pt!X%A#uQEYtiZJX26Mh|Kpoeh?ku;@}@|gaD8kbHE`rD1QS{Af9dN7^7pDxjQ zM#F6pm%}0E0pD@AwR_MmPXL|}`@)Fd@qx-~niSRmsnJ}*mt3EItUqYl>ma8EbM{sxrvEUryD;QSG$^h#^f`T*X zK6%2X-LMkKuJkfT$+LW;&%GlI@}d>Pc;Q&ssXAV^>&yb5m0S(im+Y%tdhcvfuEeZj zq2MH<3DKgiKJ4CiqN8NDKGBr3lfTCA8)nDJmAr#Pw++GzNuw(-j*!awG-0aUtqs%H z)JE~OR{yM)t_1*Nrx4!Xn7JWwgm8>f$>C!m61pas(CV(}1F%PYzmRZLX;TE89K9YL zAGDgdyTtqMGkZxAdvGH(Rv#Be7VI`ZdlBg$aQ@0}#`qP;;E@i8P;I=0;jxhI2-+qX z3u}Ol+|fFQ^JtpZDLkT?Hd2!a1{^gaT;t$T<5J0DTV4rIdjsSxPr5) zw0q9HEecmGVpErgSeT+`@h*iWoejdBmnoxJqD}*x56;P}SId=L$;N-f#u|(H@QU65 zOBB+mKXJHt2LPQ}l0WIqCIcc@yF9*(_?+51gj?I|dZL@8TTZrU7e zj;Pw{L>^ylYf4Zn-xoG(DgIT(dI~d&6CiK{@+}O)rI7y z+3JhiJ3aTxSkLZGsK%YTk{XM>CyvU$Mc3LiuE`IU;bGTkckfjG-F60lS`})ktK23< Qx!l>~wsYJ04+{&CN+l8H`~Uy| literal 0 HcmV?d00001 diff --git a/gitflavio/objects/0f/57c04a93e542f698d9ee8d5662296a10dd79f6 b/gitflavio/objects/0f/57c04a93e542f698d9ee8d5662296a10dd79f6 new file mode 100644 index 0000000000000000000000000000000000000000..d15ae606ab8555f9a0fc0e0927bb7e0072b17dfe GIT binary patch literal 1043 zcmV+u1nm2G0nJuRbK5o$?pePA$%T|_Qc8NsOeD|Pj%V!Djy;hw%`F3wD+x6Ppa5t| zZS>!}%a&4>xQK*0GQL ziH{N^w8QgWC!FRoaY9KJcG+JYhQA=Uz^pSOal!je@u?AVsN!6U(1LjabCaGj12)gx zg<93uNrjlQ@Kr6tzJF)0JTU_XRax*F7>`jf5u2-7=M&8E!cfN}CT%)`*Iqd1P;3`Yk zz*8_`@HMJFKpOof+??#}`V{<7$0a%e(naI72D;C+&P2i~&%IT&TrQ)jGK(lT8OTJX zknXE=VgV|5n38w>e&5Sgkpr12p5j?kmZo-;Ci+nUNYpy+a?6IpA$!~J`z0#n80KVM z)xG{wKdCG!cN`6U9NmA4H8&OttyoO0q=hl8oR^FGB0b3gx*Xd)Nx<4erfd18H)0_r znCtP~E!qqQR&&Xcj9WW=?a)+I>0;AmG*Mal`fMPYbHSX5k~t)g__-&}ijGedDlFUk z{?|c|6rN#E*M|M)hCzmXUygu(P`^Lh%aqF*nBz{a4m&vIB7?MYaxgJx1H0f^)&xii zSmX{c?124TWp{z8Np5$ghl1=6E?aJ3byQ z%!|8qJsv+W%B?p!WkwJ5&DP0HYdp8pdwfD)+@ z+(Q4O7(anqD)Yz+r=Jm?1(}9{NQcx9<&ysL`Q}nBaPi@0Zve1c?Z3?)PzossC`ZNq zy`;6Yv!iLZmb)2|6LXlVBwwK3cn0n&1A4t!-=GI2z#&qU_!e<3qVl2-871BpwhAP! zA-3LKbl#YYzmao-XW~yty9nSV9M`gk<Y4eOmzXv$w3?4~6v7L+DIRvU!>d`g&?k67EhFOmwL%2}pJj=o$ z-p@TbMFpVDH!G@A6(gWu@nr7tsq$n$GK#u9`9U419tQ;5h42Jk-Z$6jUo9ekSA$5qZle z8sT=4KHT3`jPVvPA{#tq?fv1o@}E?B^H8a3AuE#MaY;PGw*6D~1{vg*Jhg>A3%>BC Nv*}{1{0;dEz(Nuv2W$WU literal 0 HcmV?d00001 diff --git a/gitflavio/objects/11/2ac1a3d31de40ed19e31ebe2a09969a91ca785 b/gitflavio/objects/11/2ac1a3d31de40ed19e31ebe2a09969a91ca785 new file mode 100644 index 0000000000000000000000000000000000000000..e2ddf50e74fd21446c0d2681351702e18a9cdd70 GIT binary patch literal 1266 zcmVkWPj=WVn9f`c$=8_JT7O=Kq+KO4g$3 zG_weO#PH@=e&SM#W^KnjUUALKwZ+}C;TZ)V6vdeP2&olv!>dX1e#3=4X!YR|xY)@3 z>#nxYJi!sj!i_DmIUY)&jdkXkt=n|PN2M&as`L!de3oE1NwMf9g_A_LQ-k!58+p;V z)%}_i^OnL8B4lK`RS{iJ$jNtY*Ff|v+Lk)Q$>OY!4W^=zydtaovCe^K?9G^=0C2@? zAuAv?qFg(V;FW87K;7yC#0kxC7RW($YdQ)WNOa}6f9CF{jQWv8vL_~5wrV-mN}pmR zRpSt`dlE9lqxr7)ubL{s2Y7H|cpso~z@XQxn9h(*4=@6A#=|+HD<%2ya08DdjC)Sm zN8?LW+0h*^HQyq8tfg9@ufk$Ve&=!%6r3kR zGvE|aC(`x4!z>xKengnY?*EdNXwF&Z5X2TM@V_w3ol+4ZN?G?H#_b0v#_i{BW=V{) zt*Wxz$77tj{)r*ac+I-jVtil;S*0;E5@)CvSutIqVqZ80qZF9Q$@U{JUAqJpGqVM^ zFQ|eLSmvuweXE*Gx$l=lF=hA9u%c2m)mm*2tL~()`@5eqowyF${okfs=hfxbM;U6g zaz0S=VUMghed>6J&4ClQP}1D-F9`x4v}K=3^G_WZBRqYuW4^njwd%CNWQ`A|8OHI# zJH}SWvM>}7ny@BVSK~0n6S1N#+#vszY0Ck+#bb&SD$ezXr4HUVOiEf|HN14r@6zIs z!-{ptIsnAPPVeSuA3s-JcxEs+A$9}D4PyW_QGCYw_9jJkY)MXZ#;V4S1nGalO;~*BYdG$w9pp<%X8*2@erGI*_r9{e=Irj02IGu?&wXs6V-VYNy?+otZT?nssEbRtXtz|j^iW#M5G-EI{FfcPQQAkORcX8Fr&1aZX%v+V(%z7rWZ{f7Gy4Zyt)Vz3iCy+UYD+APy@uBppZ#jXdbtxRVXCt7i?p13DxhjIOX594K;{I5l)aqnv&KjB tbrwhNdiHcV#=v?MH9&I;?tkuS*KmJ$$nN9u(kUDnmWzJ20sy&0Q{o2{UV;Ds literal 0 HcmV?d00001 diff --git a/gitflavio/objects/13/a1fd4350238bfc24cfb8541036f0bee8e4ca05 b/gitflavio/objects/13/a1fd4350238bfc24cfb8541036f0bee8e4ca05 new file mode 100644 index 0000000000000000000000000000000000000000..4d6ac812493f5acbb02e38be9f9b50d0a46b34f2 GIT binary patch literal 1630 zcmV-k2BG)#x^3mK;Z_J414BTCJ)m&J`8l=(6O@aCqkM3^NVlDLnb*$&()rj*>;vYW4T_TCfMb`+AYF zYw^tW{!SA}f^^O!n8gBK{C;_M-;VmNR^*0ECa%ZeMUe1I?eERCm}>t|Tdhs&UOm;{e^X&#K8=}_JdSXj7r0WwEBuEq*|DeaS77fHi+ov{-}P@iuHD|Z^OY&d?Ic&;zl zWX9jIjnl*?n+q)c09hv*IzOWocnAxm6qzuOyMecGh3pW5M}`0>>xdNQ0|J>T2`UCR zDoE1uLj~7ewE5tmuNE7^=qnv$ZQrt$i}JFk^U33l3-+7Wk(bVjx-J*r+wZs`R3*(JN|m#X_(e4fL0aM9rqTLb!JaDDe;NH0*F-oJo|k6KQCw z=m~6cmNED3qm61mF=?o(T9mXG5j(>OsrTsQOjuXB#YtLPpI%$>L@xo_+&8hJFs9&2 zf>)2%UCt1Pp;s-9sR`+nPgkvQQKilJd@$fL3-2@{(dc(_D>Vs=T?KQs>c$vQ)=@0$ zm{p5%@_!OX*!$2YifSJ^nEx)#+J|Zia&`XlV&%9h!Id^nn|6Kw6++IPGkw_RLXAs^ zVg<}BME5^xfj?>is@uPZRiaq&WwyLHE=#^YUB6+P6AK@!osC4K=4Rh)B)$1^C0xo! z7IF?P`f;aYv=T?btREf-HdopZPJB9PsKf>)S=2i+qb&k z2muMcbQN~B<|ZV+Z&(YV92eDkqdF>xpSwCBC z+B6WKqvAe!R?6gko~oY#>MPLIn4iUIgIM2+N7TI1x9n9q?&znsU@{G;R1Hdw=@Ja@6U||zzg@)^KovWB zGKE_Xa?J?8;O{JiqFgKE%T=wd)17Iakjl`Hac&oU)FkaLUc#X1*~^Pc7$rU{rL|cC z6#r?^qYn3f9f0b%CgJi7e?cj*TA$l+K^?M1>6_l>%N+9s=(3Z3-R;WmXZ<$0`EGf# cNUo;JqIJ%>ZX1fsAy2cu9~ylBFT%tqEE8TMg8%>k literal 0 HcmV?d00001 diff --git a/gitflavio/objects/14/e1027489784b717661588cb43caa419eec86bd b/gitflavio/objects/14/e1027489784b717661588cb43caa419eec86bd new file mode 100644 index 0000000000000000000000000000000000000000..ee6dc53dd18b0d0d0b26f45d293f72f6e825abaf GIT binary patch literal 1320 zcmV+@1=sp`0o7RDOY2A!-)H}dLqO_)sB(L{u!%giD>3iE z@$=CU-Uy|N?jcesUbJ-R$6^VQWX4Rq2+z3orbmE3{!Hlv5^fhNo_Luu)&mw z^aL5Ri<4fiLl)iVSzx$cavd19|@3!o<%o#kRqvxM}Iw7GMVE8YE~+9O2_c+ z8}xc)OEBVJd_5X=3Aa;_%1#&Pv&t#^3T?RLk*y>ZCLs4Ow|(YREc90y0mSKZ<{{$5 z%qg{|K$=;dbHpFMBOW{^^rkUhK7Mw_{)d~&lO@@lA;okH*=-#Q5NyKbJCAa04h28$sC9(F4kWY@xF$ks&6qTwjl#A7KO+*aRKzIM zKZZptMg^4`8r@M7j`h>scr4Ht>ZxxMCZ$Uw7sjYaFw4O%c!h6R39KsgoG_6Nw8Bi` zc$@-$!L0$2g2}_aJ2&`|l#nB0>_Ql5eB|P$qYRb8>>oTgT3LmKuY=@%wWpEBvx@55 zV`yP*)=cD%223Wu$i;+or)()W-lZ4atankOMPpjF2~9<%MTOp>v()V?m&0DSRJ4sZ#AKSJ+Psy*wKvcQ>}Nli)D2VDmER)X?|LZv@sAV8ui17;%3x zW6>UQ^SbdGQs-K3i|le^VQ2;m&1dyh0)0U{1hoI0+ld#1lBM@(dM>>YXv{P zUjbV(A!*~(is()PwY@{@eLI01pk<(>W4YO*ae5shewbT}dzW&*R6ev^nrV@sLm`v- zT%>)sy6clLeM~_f#LP1EJZo9g+f45W$n;TBoZOYFW8- ze<^e~2cLF-KoV`sj$)VcV|SfP>rJt>$GdGZZ*gT86!1I3(uQE8HCu={=Bdl_BJQYU zXgTafhE4OmKqQz;EEbW`v%so*BEBgXo70o+9cu#sIJG8Xy<()D>@F~@x2d;oaqAE2 ep6~~>1)d)H16&hNX@QJOq;iLhiSs`)hxu$D@}+SmtJ z=cpkS0?QIJSXi^7LzhbpDASYA3!FpPFENt$!Ic7k^}hA?e>|)pOv(j7%i~ouG`@dcqp~-uc|UE4@og6;vCZq zZNP^{JJhZ>doC~nhfgld-AeUA7EsyaAB0Ok;RP1m!@uo0_{rHX@b%4itjiq;=O)N)%aK#;~I@M{gZTk z_QKe|=PWIv=CN0_JLc+JOY3cp(a~+%N3<{*u}<@u$c!#BklJ-2=p^H_*)5kD{{g3` z@XqJs^VsQ)soFYjs|44$hOsK-bsf>&yp8DYrk>_w_7--Sdhrt7Ij!30^A7no%i3_8 zXWWoPut%HHj_kz?i7G2^xnDC9dq~Ou$s>C*A s&$ppzqX2CjEKH@d9nUZ9FW44BySKe_5?~%>h8rAh)9Y3H4e{4XF((f{@Bjb+ literal 0 HcmV?d00001 diff --git a/gitflavio/objects/18/5b273ec42b62b8ae4f74e59eae70395cc88752 b/gitflavio/objects/18/5b273ec42b62b8ae4f74e59eae70395cc88752 new file mode 100644 index 0000000000000000000000000000000000000000..4f75d83002a7f905ccc23b1fce4ccf37cf24de31 GIT binary patch literal 4528 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 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/1b/210e4c7201ee826cb4f0e479e5486307e9bf7c b/gitflavio/objects/1b/210e4c7201ee826cb4f0e479e5486307e9bf7c new file mode 100644 index 0000000000000000000000000000000000000000..d916583c50905238d4963f10e67e27e86e9c3226 GIT binary patch literal 5182 zcmV-E6v69w0qq=VbKAzXpYdAh{GV1_2fT zrKpMjd*89U*j*eXMas=l&e#+ed%S)3d;1oHI30*DzWm$YzWT%dH?zrXYisY>vn}yV z>~89VlfBE1!r_PcOw8i>O_Ydnnu*tE=ck)qXm4vP38yljg+nP`$Fu01*8A7HY(VRO zy|p#Zr8rKf(=<6by87;G`hAk6vr&4N@UJ(;H0Hl9Wu8Z=YQCCe>0LO8B}_7m!#o#< zc`l2qa3JK5g-k}dHSg9>TLM0_`5=ylA}_)MzB*ZSGbs>2aUjmKbR5O9*Sk1<{pPCw z_VBxxFfk4y0^lFd#A{iI;(jIt4th>c1yF|{{(Tl^;Z#IPAv(!Gyq!-6GV}XnvY2NH zK#)brje598-!5$9c`__uui^%_1S`IU1-m%yf%t5@XOHKdAqZ;je!m-N0tY)Hm@8xl z+h5Z{AM3XTcq3FGFGPi$Hf*n?mFQqM+C2Vu3P03_ghIt2O=I=Ea~Bm8=rN1KLW=L< zhtR*&ATxF$iX7a814PZMG{g0~K6E?6UAP5%(O7h!9H+@Ry6N?z9H&E4zf6$FMqS!$ zuScA=y)8u_9zDG(}9_jx#koUQ215KhUOM~#LC=) ztfn}qRh&k=SpWh^jazNu90m4)_`IHt%$g-LY=DWzVVr|b)5u$kkK}NYil_Ud=vENp z9t2@5vqI4SyLVxhfEEf~JS~^QWogs|w64sD;Y<=w{gBSZC>2Rsh)H-W#Y|??h_taN zQgIu}yV9sc3Fwe1sMs`l?kXU~gL-cYygh&qVK95f=ZeXTY+jEHl1I|T2ds#cY?fQ0 z9(g|+fzIp#b8QP2;@ubHNXDc5I*3NWd-2UTpi!;GJ8_hJfFYMMyOkN)lwJ?C@EJ4% zvrq$1gsTH*pccesb_4=zPgO-f+uL2Ol6S#u$zm56jfh|nfNZ4TI5+@liDO^m(s!i#IEV@M#qtn&SLMN0N2oR&T}0hI zJ@hs$^^9T~#W9VVrioKkI|yGawR(gf{b4xDWPcp}D67ys-7m5i`{29`(-^$k8h5lrZw?{$4S80sm!=4%EwF@P4o!cLgp76-<27~%0eA(yA{_(Hfow!S57`a)Rjw3*u0I;}%VHhJnK7sw9leB4 z*|wHS!=uxarda3Q`N`qc%Wd(eKZ(XZ#}E)fpHD$nOLS0D4mHms#x-Sj#9&^C`&RE4 z4baMj`Xtm)WGvd1j_y^_`GggX35A`Uc-2t@tC>Lkn53hFV3y`ZAVM;h z2Z3i=&(GeyK7HGNcX4!h{O!B*{`W7B`X{FsXn*#`83c5DEGjtTbI6`OB`sdHm~SAs z>GdYjXe5(v(7%DqRDYOf<9?BTkO}PPI=BtvxeUa?3kU=#&LRKpt-L$Fym)nm-MZWF zEg_`6xbtFQfH8tMpPflqVg?K?ycsx}p?Tb32B$MxSZC3CJ@jo~W$E-V%fkDv>42gf zur<1Hz7Jtz+-Y*3+Q5| zprtTj6$m6!0GSIO;P#-fm&h0libTM_-B}h*!|XmNfpgHtG#tihj#;H?w*o6VY>oj_ zY^+=z1Pni>RjW{JTpSE3ySqXpg!A%;ID*rlc1)xbEFqGApZ&NUF$D*A^nyb?afT`k z@~MoxXX@)=QPd^HayX+7NBtOTM-V6~ysQtX6doY@h13F1iKH-F<>wq|>F6F6@pT(V zSEGG$-O9Q4!J`mA|16#;Zr^`~-j4DjU&$XZc*BKT%y%aQO*jgR5JEl955#FWI-Er((8h5gWCa*s zWqQEOu6TaUkm}0?Ar)lr=eesHw732^ffVzyGJy39*C_GOO8rYUG9$TEA zi*N){Y2L8y{H>xCEFGBht z2tb}W4qpzB=oTHv5hRc4#AHLDqPI{2N|uelqa56IFr)?hW%~lK8%VWBvgvsAqyn|v zUk5VF(hTyVPawL2HN2#ulJEviw`@k)s2Q`IrP_^d1VihEGhU!!O%u)fdht&Ny1nMg z@R!-wQ#gmnk}$ps2YJ1U1x>5zqt>bV5oe&M#o_s>Angqfu!{PD-iwo|h;sh03nAEv zFm3>!Y+iDbR(DkcSfrGR_C~aL9D;lf)!z(LS=+jCs!w|s*S1zwuS0FX*e}-$pSwF5R zkPD1SV5+}|<=w+F?1>+@x1v3E*AQ`&2NI1mQ6X_^$^8!SvIm=&pZ+m_Dn`+G3_g>9{KvEm8q`xSNN^?HUQcE9 zqghKA3ndlY;#{6|V3TFA@h5&fcGOyi7}66T^~@I?o~Qg@&8SsU%}j@&t-h7_OHSme zEoZ(9QfXX01=d=zJj(Qr_#DrXOR)Ga1srKPXC_t=XQ2JOX}GFrT7Y}eS z!~L|5;XynI?|7Y#uf=Mcx1~VE1}hC@sw>2^c3&T}+745B(qktX zO~z3}(NM;LNStGxg9*5do5ha;a3VGO0CDUh*Y`J;&GM`iIVlLY5B4`vbHE0L3R~(W zv#!hl{hue*%_^VLH!T|c_VXmfkM8LnVq1#Abg~jRw$lU0%mjph8dxt!; z=IG6Gn7yY;3vAIJGe+m?4UMr!TfP)>=EyGB&H!s4I@9E6O1&(8LyXd4)-s%?$8h>b zKXh8{um)$r75|ZdP)g84{}_+)FIN{~FaE%atd;SUEG&4~B{4qeW%}MOtqhLA+0Vip zC;}DIg2TBbXTOS|{JRLJ5Xt1P&V3TVgrdd4Iur zs%n|eW^bb+l3p^Jxy$8frn!exJlur|?lpa@TVuZoitVTAsF9BulST%XxH|-7aw+b+T0KlgEPnlMO+^#Fm>dp#M*6IQet3o>J9v zQFE|KuG_D()T-q+0iIkKe^1HDFUa9*_m?qq*hG=d6}I^bpWIEl1_U7m9ZN!;r^ewD zY8Dkst*XTbWdP5jcr-7HG|}f&0bf7r^SZFs3b26w_e*ltbmcAEg>-77gepUP1lx_f zHzu#@yJ%r{j+vLlzMl3w@NHgUT@tIkGEjXW?u>r4;b^z`cF$U?eSt<>3M04D@sr}agKnPfGW#eHs=?}l= z!xq$aMLNn=g{87Fn`36Uf2yXoZHvcs%kCYwjGrYfY%Jb<&iJG6q9tG`DKgF+6^@KihvNp1xkn@mabQ_Dm5%!pb9t} zz*{!aXtAW><|Sf9ycV|y$A=4oMG9{!#l1Sr-DuL?JGb!8q3Sd1YR7AaP=Bed4COOf zm6vR=vSs{)?1LW-k&_J8jgvZgzQS65Q!5BvDoF{*uzNQaP?S#asO$mbE;xPofRUHc ze;%}=GH&uJKu357gm6GPfyICoP_i})#(%DeMaZQef zs%*BBeKkwvz=Zw5eF1r5cR75p*`+M!Z?h9m^BWI%IL^Q-ilvunPOoB!s;)FPbNlMI z9QOWf=0)jHxF-sd$Zv2cufi>zh4D|=+6Tf}# zodkCT^1KHD*pLwM1#+VSyiisL>XvnNnsr8f~P3h>04mTNvLO%;A_urG9d{kkns*LBrO|?V*H~7n4apdSv8qD z7```$i$l(h&Tj-~L|gjtX`$e_R?x4O-lX4zIKI4ORj0a|5f3Slagw(FPIuIM#Po*B z-vny;lOSt#?9pC4>K$IK{j1e$SnIHh>pQGU_T(?hHV2Ec{tMwijf=IG1dl{X7*)>of?u zKI*3e0lbmE#Ovzx0wBLw_(wnM1pSjn(pXC?3zO8ZO6XG2wJCyFSc#^Q&>8YO=wN`p zJ*%tPyb_633MrfyX(^BH(KUl@plXESw{^Q;)30Wu?NT4m6$iUQ27CjnGSPiozz;Hi zn|9rb8c-veYwUp*B-Z`?OL$2f6mEHiaRFUlq!OWEq5i0x4!WwusGSB~(x5p;)w8Kz zum14Q!akk)P-yJK5g*n+TH$}GM(jSu$+cMS{|dr3iIyDc7n7iwTJ`lG>nRAAWEC-o z*PlVa5<;y3-F!X>oc{O%35d_X4CuvG2f<%ItHj+Mdw@O#R#U#%uM7TJx_=d##hhcU z7;o54$Xd>Rm6qvBDlg?vA56Vb+5y2cuyy~lU~ufa*=rd^3yVYF-;9O4CH&SG#H+C~B8i-7<4H6xyuND8qmt-kosxR}-Ad3o%Zt^fq ztvYynktTu?DHsfV5g-zZ5Lh-<-(Im2Y08P1GUe|^!hE}sD6imMsS@AkWnG+GKoiV` z0!gN`-8lLnxw3f~;+RvWO%cdU*xeL;Jc3nT=Gaaz}_n3XhnVyUkΕ-/O|hSN;  JPM#p/G)ׇ5x)hu[ +}`*Qѝ0vA%᣶dt~iQј8ۨ=G5G\aKT +ULoZ0ſH8Αݭniz\x'#F$DҌRR6 +*I;(Hgpn"3Lf%92 CTiu# 16˥~Dp6x"P3AqеVw`VW^nC5:XmnX:t}.Ri 8S1Jbyŕ1Ő&&^w2 q1Z/ݤO}Yc9(5MwWN \ No newline at end of file diff --git a/gitflavio/objects/1e/bb463168f36028ae98c61581e6c8ff953617d0 b/gitflavio/objects/1e/bb463168f36028ae98c61581e6c8ff953617d0 new file mode 100644 index 0000000000000000000000000000000000000000..45bb7ada39f07a030be2ea9bf6830ce6c92fc328 GIT binary patch literal 64 zcmV-G0Kflu0V^p=O;s>4XD~D{Ff%bxC@9KL%gjmDOUp0H)hoy-h>wqF$h)w1Ie&VT WN?Z@eOzCT7|2C^`GzS3oqZB6>jT&+Q literal 0 HcmV?d00001 diff --git a/gitflavio/objects/1f/9710002aa168ed7592b58b9f2d9e1f3431d30c b/gitflavio/objects/1f/9710002aa168ed7592b58b9f2d9e1f3431d30c new file mode 100644 index 0000000000000000000000000000000000000000..1b296a0ef9d7825af448957ff254c76bc1a730bd GIT binary patch literal 1424 zcmV;B1#kLz0j*bUQ`6G-MFhN%cRe4XbQ=azqMK#b2tqm z@!1Q~nFv3hNqHCV=a22vks34qE{#67(beRNjjJU(GMtK8^|_~{4!&Ao$kjbpkDVJw zs)a6C9E*G2Hu(Y;Ll%gOqj0=mt4;V+guG6?^U-PVl8y!++MSOhk6w2N!(RWAYy;V8 zylw0fOj?!f1uCANdp#}u`gLQcv9nRS{nx7W-dWy$1iv?H4&_!F!7UQ%Y}dD1nPdj?8TK6u;JK~aS-<~r zh&j~wFFd<3ux_gUi%)}*G{CLE_j$Y z34QxgLq+(vY?BpLpMFg7g6%PUR2LHT1!1#aT7E0>;p;HO2uLptuJ%$ zl8ZF2m5Lofd_&YUVlz(4U?x)~!=#ysG^SE58aUR-rq;UXUXJKhzkPgMHnxHnHd%-a zfJICh|6Gq0{j{As8g}VLdpPV}cWJwGg``}*MuTdTgXkIU42Na474M>S90i@A$HE#g`}D;Pr=Bw0GHCGfpyA%_)*bGEOx2V7ajJ zmW4fh2>PjE-zSr=%R_FoM8K{M9A7!y8b({WJtKac>oM*x=!b?B)4NRB6Q&gVeh?>0 zgtKi}h;Mvy^x#;qw(_vGV@A35!=TBIm6g6~(ZE+LK(^2xMr zth-EWgkI_Q1-m)fI>(@|f}=zDJ#JJXSKFxaA-}|qDlKapX3ByXc~n2Kb3@OupA6OE z0Lam=3H9KoBegF4?@0Bln0*aEim-? z+i{K1#$R}km}CosS$K~18%uoEySAS)O>o%%k$JKARS2ks;<|& eWdUp`Y50qR$qoH%IJx5E0p+5_kp2VupbKfhN6LEu literal 0 HcmV?d00001 diff --git a/gitflavio/objects/20/4ea8b1044799df5f383c9b7489ba8a876a7c03 b/gitflavio/objects/20/4ea8b1044799df5f383c9b7489ba8a876a7c03 new file mode 100644 index 0000000000000000000000000000000000000000..564da27c92026337d54ea0beb038a4315f75125a GIT binary patch literal 70 zcmV-M0J;Bo0V^p=O;s>8W-v4`Ff%bxD99;I&&-Q2ElNsE&Mqy`P0cMRsnjbj%waH1 c3d)iFq<`SYwQVomtgP;TRCBZj0EP}1nT1*$k^lez literal 0 HcmV?d00001 diff --git a/gitflavio/objects/21/5277ac86967035797e47da0b5010a87ce0b98f b/gitflavio/objects/21/5277ac86967035797e47da0b5010a87ce0b98f new file mode 100644 index 0000000000000000000000000000000000000000..9510d6ce17d4f892130f3d87523a642b911ca1cd GIT binary patch literal 71 zcmV-N0J#5n0V^p=O;s?pWH2!R0)^z_Vum&6yXQ@6ZnTa6xV+Xp&%e?(WpgJ)HmjIH dz3Ph1`)@V>9H)Sw1FfcPQQE)6SPAv&ZOwudJC}5Z^lzE{gx%B8(P0p%m zxnEM(zAGJy?MrL>*<}F^0z^a^2tw5EXmBz^GMApNG$>z$#~L`T{qnAp2{;L zTl>jksj-dj4p7y;iN(d4WvPzIAT40UkNCbb3--yFynWR7%0F=RqLr?bWuS@!it^Jk zb5g;|xH7z^?XlHraP)c@^>%x?>5`w{+M&vVQ;W)=Hn;DK?^-@{?p?$4U12F-&&6GF zS-TZQ6*z#v2D*tJynZI){ht3Hd7D)iSekrwIGPDn8d{W;n4Dc&;8>99oRgWFR|3|M dc(-b)ZQgF@Q%rq7FBRnds=oOy0{~3Jn}VtLtU~|* literal 0 HcmV?d00001 diff --git a/gitflavio/objects/25/9e2ef94ae4f4aa5f93125a6551690a28af573b b/gitflavio/objects/25/9e2ef94ae4f4aa5f93125a6551690a28af573b new file mode 100644 index 0000000000000000000000000000000000000000..59b7108c44e643e25589621a709a63cf8234074f GIT binary patch literal 1632 zcmV-m2A}zO0mWHcZ`(E$-m`xN2V{^OH1Ps7*x)YR)+7yr-m;__Ftmk0OSDZ?5>=8) zV-)@GJBOrht}R>6#eT9S@_gs=oeSwvEJoz_!`}{n`s2@Rn)P~v-Q6D9C42YvL1EYO z1r0yv8Oh>&!c#I9lAOK1IKOWoly{ygP2+fwXA#wmWKx7osS=VL9PGm~T!|XMPx6LO zB-LD`B#bF6jRaF96`HWyOejV|$w1P3I*OU!AJ8arUr`P@7D)!1;hmM|EQDv8$#m$T z_6Se8<}@ZMoU(*knFu+SA|Y4*ye4A~B67vk3E4S$vG-i&=}UOz_Yq~U*vD=}oEu&3 z(n@#G>m_G#$CH> z!S&^fzmH#CUj+Z2US6HQdkc0L4^%t%o=03|F`c~uZqo_#gTVLZv&^YssO>!=UZEej z)CpXsxMe)pVK^ID$M_Zes{Pi%L1==n0`M;oxv?Dsx(wazhE!x)&oZ{D0LA1DlXu%~ zl-N`R#2*>CaQhDd{V`KrkZcMKhb&dOWG9xH_F-=8mJjhjhq`1M4qfCg`DCgUzBR8M zf3?gZ->SqoHs!4>Q1j`~C{SfCWK`0mVlJtITYG5KNJHj|V39yKf=BBm&|)5z#jJ)3 zDQ3jl6q`U(gIWu9w8$LEzT{9BBFjZZj>re&6Lgz5fx;Ecv(b^)d$M9(Mbni_9U<3T zXQ{VxaX~V-@Ka%N!WA|XYj7|YIh3^{GN!S@YKwM57iKh^5`SmjJ5-S!le9(c#pR|z zo7QAro!)W<byAiCs4WRFaHKS0$S0+Nk`wq=tGPEc&Y4G!o)fQL z;^-L0X8V{&MM*12)sAK*0-R>Ix(Qa3YIQPv{j{E3s0u8wG!rSBqj|#MjFDtGU?Pp$ z^)mYt0oKfIU9=a+_NM;;qw3X;q*c$^CQmCZ+=O@kRIMNy#qvSe$~8-H+{8FN4r93r zD4ZZ}3?v>anyjr-o|i2}f(Hcfo~=ZXKZ7 zFv#lMB@8M^IRSxitW>X_=30P{_1u+y!0$pN# zvz%sKFt#^MnY>?PlB)N~HI_Fj{Tl1n8Pv@2L2Z9pvwA$(Dan>t3-a*iq`CX7z@p!6 zxafM;FDLqq2a7fo_!?#9n?0pRTmD+?&Jy&nKN4}UG0C-ah{Jm~^!ola@sc?PaYuC#AO&{yysZYR^Qi#q54;SmKHTAi;8dr@H~`xJva{Ea(vH?e*$N)e+ln{e?r-J{1&D7slEKkc_yIja|rH% zryx_+B76<5!k6LK@K!jElYMX>l=vHtr=i62JQROzXTJnr<^5eKar_2F-@hGq;*>{q z0Lu75DEgj);{OdO`u3pcdj-n8Po4b>$7@jH`4)=5pP-!QIuw2Xf}-z^9kp(Up`3FX ziodf^^nC@2z7~qU??ch|W5=s*{3|H>{s2YaaVE>YlMt!Y6qNH!L)m8@ioWNe=z9T* zzC9@V{s=junj~1!_em)6pLKi*N<1AX{@!u+4k-GvJC$o;_-g>D(Blz z7yq(nziA#0(&e0!bkU*T2va>u-$|D^UT}wE+-&9HCu5XYi*qNVnlFeaBYGigt7cjln<95N3yoTc(=`+JPw_&wK@$-y zirb|neGK#?baS;o$dF{F=$Osf(OHBs&b2SO_5RC4Qtj=j#VF#@sKvCxITJ{7>fm#A zZez|hrLkNUgf3HZ5OkzQ)H3B{)Cy;)cGGN2PJEr0twz79JT2qBq--Ng?G!PjU@&hgHm~yT@*PS(rQ9nwEu$}%req-&S@=JDess!HMZcV?ULF%p-uYwvuDP*0tNYLtX%G4E6k}M9p9c4R-}*ERzB&KjPs2Zcj0DJ2#4Wx6 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/35/62526c1df22fc0f8d6b6e9463a3adff126413b b/gitflavio/objects/35/62526c1df22fc0f8d6b6e9463a3adff126413b new file mode 100644 index 0000000000000000000000000000000000000000..4cc94a1d26ed20a6f45cab4e9d5ac2b9dcc98a62 GIT binary patch literal 672 zcmV;R0$=@j0nJv?lA16S?XzEzx5b%#&{jL`bbMenZkYmZF}CYdh!`=mKo|(_^zWOX zfVQaUOuIfYnVf`k@5w#)E&{#)A3EL7?}iT45d=toFd;C;z@8ugpN%P+0@n{>hT|af zS%kA>LA;kN#vYGW%uijQwE%G0(glW%z|5W+k2W0T=k1wk0)2)i*s$gO7PQg50!JjI zF8HB4!>_udz3=zi6?_E8`piV&?kWeh zPaywsJv`)+=^5mcV_3Q~2jAcvXezX}`kh+ssB7DAyVVBJel%=2FrtW$9!iQTuqi}? zOsqbIiyjzGOjD+}SmP{W9$!lG3^u*DX6sG3sdfwG2f8t zDNQysxk_>x2__g#@$t$n3aFGapDFH2+hyHTT5sp0<*T9GR+ib1!G7g5C{v7HNVj7F;(? z6lWXDnJ|AXYW(o8p<*`_=6Lz=P7EIZ*BNHE@qGrj6XGs4VK6i>Ff%bxNY2ko%S_kHD9O#yD=E)RXIRWA*BF}Dy#CO^ TU|o|#-^EVvv0(rJxmps-`{Nt1 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/3a/3c4d169ec5ca603e940c6ee57305c4200867df b/gitflavio/objects/3a/3c4d169ec5ca603e940c6ee57305c4200867df new file mode 100644 index 0000000000000000000000000000000000000000..5c98ff5e551abb101d7c91041121652eae7ce843 GIT binary patch literal 1447 zcmV;Y1z7rc0qt4eZ`(Ey-n0J-1~VYLNahTC80I!9oF7gzq-$U&Pe~vs66Fw)NsXk^ z1#ABI-I0_;Tb7fqX^R&1iyiWK{CM}>@p!q;#5MW-x9vZEdi6TL$vd5d##tn3Pie_w z9qRj>s-0i^FFT#Vix(a8f^0q42bG=6y(qpda*}7ojB%0*NsdlVkDq&^L8rr`IaPTS zQ*xB$?9}Xcc`mQb{+IiLYbmmfN;qkuT7^)#N&l$?pMZe{S8f9UG8g<{@9eET%Nsq&wB0*}x7)z(|P4#U>&jOt!c%EG~ez}v|vQ6WoE3FCUb z1{4Gh+dd4IlAZ@;X9qSQA3j)kM)Rv3vW+7f#&040hbgVX>0)b1Y-N)oceFKBm zF36X!W)edaAR4FdYxbh~DnlEiSuw_yj=+H;C3Q|PG&1vs*JCN>2Yc3*NVR98(g-t! zv0{pe?d#dgW}~OXcggTNftfI`46EKV9p6BtT9wNo7e{b;1+m~9;wu{J~un{{S^pB1i#>r3@jE`4$~DOX}v zu~cw~XhO88s}H;PqwFZvtxh!M?D1dYcMY@S%eVQ=U?$(Cs zYiV=vl~(_(maYMSu~P`|Z_M0~I6}BaspRluE)u#Tn9%C3=mWAxe7}%zRcTWMoSz1+^g?WT-oiMj}IKDZ*UUoTd2r5gVmQElzUHxZvv zTlG(=#_GP;^)`b7X@ORus~wmZMIOgFVLkNFCz`ng73G-FpJ>Z_fk1?x!NsE6Pay0qphuo}KN`tPR zX^u8W+H0PKX7vBoJg4WAqcu;5A1V90OxZS+q0|s}uEu|AwruhencW<1jx6O?uj|=t zi@M1*efgXXY>ltr#nEWX=ghqTaMtU}A04(g<>3)(3qZB1C-48$!E{pU_H)%NYWC)< zo=e?x!dKc-&m8{wA@5z!-3DA+%e`JN&$xWx!hugcj^2%7x?haJrL0qB6 zVsFDq`A^{bG>tdzM`QT7H?VtxD!^7G-5C`FfcPQQOL|IE=kPE(JRO(U{IZ>_tWdimsRnTg`!de zGr2U@hg+K%0D(eoWpQB+gM#0RjV$gn@5fu%%r5EN)zzL=!wfa1G!NMzSEtrj9IRzu VZ_oVCbHn!9^1>p=l>i9CEv0#FH%kBj literal 0 HcmV?d00001 diff --git a/gitflavio/objects/3b/25f0d2e7ba99cc57fd06590ccad42117d56e1a b/gitflavio/objects/3b/25f0d2e7ba99cc57fd06590ccad42117d56e1a new file mode 100644 index 0000000000000000000000000000000000000000..8a6736a1168c8605a7a4d1899ed4bca2b3ed2138 GIT binary patch literal 499 zcmVm z1C##}t^xOe5&!OYCVpLm#G;xci7r|CUO}lXsFYv)SYC?N)zu9j4%x4S(G#(k(1cY_b3ut4Wo(=Cq64ro?lZG9Hg#$}DW za4Y=`A?22Lk~o{4GQRbe(fQb*-bsAon@$}~7MT4M8IAFm?!k16AwY`}_bty|RX@@$ zy|T#BOwxZF-PBGfFyKM)&?7t~B#e&a2!_Od8yUgE{&oNW literal 0 HcmV?d00001 diff --git a/gitflavio/objects/3d/47b597ecf2e93c4020cdeb910a06b99f02ee39 b/gitflavio/objects/3d/47b597ecf2e93c4020cdeb910a06b99f02ee39 new file mode 100644 index 0000000..70f8311 --- /dev/null +++ b/gitflavio/objects/3d/47b597ecf2e93c4020cdeb910a06b99f02ee39 @@ -0,0 +1 @@ +x+)JMU01f040031Q(N-*K-/I-.+(`4zĸ(u19/ \ No newline at end of file diff --git a/gitflavio/objects/3d/5eba338b0383acac7cbc8bf39ef882ebb52945 b/gitflavio/objects/3d/5eba338b0383acac7cbc8bf39ef882ebb52945 new file mode 100644 index 0000000000000000000000000000000000000000..c2cd0a6234e83c99f12fc1507d950688dcd7a74f GIT binary patch literal 367 zcmV-#0g(Q90V^p=O;s?qFlI0`FfcPQQE>Kf^mBLh@psqDO<}NVyR>X(g6{GUq9H%_ z*3Afiw9LE%sxHXY(Z$ylr0DIL z&d+1un(^v}n7+WwXR`JD`x^WnA9B9E(8K@;6w-?F^GX=xcDos7d`{3TQq!u%X zPV(_jzozK?S9NZRY%0gW-Fr52LR1u#Cgo%%Gdx%#w|ict_wz$y8FJqWG#oDPw0s72 zWpQdrX#v=oDjmC`gjuFLvdP|^*Ks>*?Vs~IR3WAm7bP=-1Thac #N5T<ҙVkyޜU +dQDG96Ay"Cad6,mA ќGWр-I6wZfa[ߎ*9 X<:'BMEx_lZ ~z@q4" n.*389H4[Hs6vAj!:6!8.ӓ4-oy~ŔL -|ffMlE"!%]g"%I4_P cv.;ٲI#աcB +x&\ۻyi<2s"*l0z+Ւb9>Imtg/8wܚUOI.NwnOhT8ߚʺkSV4Z$?csL"R/Xᬲ +uUh1S &-떋<Izd桵؈tosc%+T6KEChK[\'/E&}/|hʦf2rW5ȿGŔJN=k#Y KƯ \ No newline at end of file diff --git a/gitflavio/objects/42/dfeff5d2b6452efa18cff9bc55ef86c254e997 b/gitflavio/objects/42/dfeff5d2b6452efa18cff9bc55ef86c254e997 new file mode 100644 index 0000000000000000000000000000000000000000..6cb370f5182e79a3cb8c0262e9a446aa49bff064 GIT binary patch literal 1412 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 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/45/4285ea080576f5db99ff0cd83dd6a7717241a9 b/gitflavio/objects/45/4285ea080576f5db99ff0cd83dd6a7717241a9 new file mode 100644 index 0000000..c0ff72b --- /dev/null +++ b/gitflavio/objects/45/4285ea080576f5db99ff0cd83dd6a7717241a9 @@ -0,0 +1,6 @@ +xT]k0ݳ&!#ݺ-k amz0(Ŗm1E2F{%+dcS{;yeQt4 +`>, 0%C&$,7 Zr1cPJj0M8 ,YI7X|)2y )('ivCRJNHywa 8uAm6}'IaσvVqײt~Da'ih*x=dj4PMZ4WaOi:^nQ]|5ޠoWۻu㋔Í +IP*⋅g6-[uʚ}yU3M+$FNgpkMki:\n1B +X[cX}w. V"N yc= +wPZ23\.jkMc * +kmJזDwq`jm5 \ No newline at end of file diff --git a/gitflavio/objects/46/15c1d7cc58efbcfff10d8325a03934f540c569 b/gitflavio/objects/46/15c1d7cc58efbcfff10d8325a03934f540c569 new file mode 100644 index 0000000000000000000000000000000000000000..7c73d76d80e94a5728b77c6b2e809ee26ec74f9a GIT binary patch literal 1979 zcmV;s2SoUI0kv7{PuoZkzt8zArd3os2ueBKLrO_F3MoVaiXiB8;*jMyo5ZSP`|J%A zwfyflGrNA^gv2{LJ3EixJQ#by82Q? zdwZStt=8_=RtvUZ=cOKEb`yQDozKe9eV7IjoSa<_U;3ckR?D{+A`Wdwz=;>S zmwev8H_@2S$LlR)0Y|}N5%|X+M(6MO_o-ZX>f5!5V>j@T4%HLKa4n*H5go>{kRZND z;ZNe+?$*~9;1e##p6fs?ZHa%{kvp495(9d083j|<6W#9B@Z@x4T^ybdkT-2&HPe?Z zI6>yp9(w|4*IQl&G=f_FE3_kff!rh4pMCR}o(F9fj*mEEG3jf0TO{N%^7THwxXXWw z@=lk&Bk|5K6LLgSm^t=6paVPIJR5as5>6js%uv&oGOe}LsONb3e;BjnsoZ*Z(Xg!1pq?p?)F8737V} zi8>4nEmB!k1xJDJ3r8xBAOlc}Tj>VScBNQQ&;ktgH?0`Z{z6*AhY4fLohU*W9yidb zQ;AUdd`6jW(B>Wofv4ZC^e0DMjDu$sD6Wh*{w$M!8`!*b9gGP;rSaz2{?RgugfDN9nR#=0d^j3(;LRIoq&d185KRZNgNU!b_0;xa zp=?NRwH@gzABknZWS*3@aAwYZ#RL;mWi+GUvIQvqb5$Pyn_Nu30x84)c?RWaikVQ*H zpeOP<;eRT&&3e4DE}DrBLCoi9qt{x9o>GXKXrvS ziEj$`>LqSaIZlQQna-QP$Sic zbVTdbt#{Q|4K~oaHW3Irkb1$bf$wXW+!YL&^XVKSX~<$n!I%?~Gz+`xoTMt;bDYB< zb5nk5Qd+WJ)e>)29$2sMG>!^)B3k1oxnsijMO@L?+zkzOK_?PeUIbqI-TJGD#(7nD|}|^1?O8#`(ETCFX`1MMg_e zNhpfX>SRJ2Ypa?(hSVjSG~cP7p@g|LXp(b#blG<`fu93fa_S$*%|)gq^sJLfr8y0~ zq^~;IT_Y%{g;Ti&SnJf_a*MEb8NWdx-&D#|rHm-pR)Sc8?>)jHCp@6<3NEw0Qd`)L zBKvV)wU^tlaKDI2pW94Lzp}>1;_574@1&5d_+=$wW|?BWpKf5`3!?XcoRbMKBoA25 zTt!sd1hgOKBBIK!XzW}%BG0N%KDoq`5OGVAig=eR{kUSwDY_ixN)Ys>wC1=&srG6^ zjpT`Ji7tZ`DHV5-_-`p|s-)A|Wq1kpWD?_Xf)iA5+MOVzU?zpHdcq)x5xwD2z0-FjzcDP;lrq^DX!8jX{i z>v{0NSeFb#VxH=$-jv2!>zWJCz2;F7P*`f)l%^OY92Kqpashw|2d7fGSx_DEgDdCA zF7&9;8|g0%UXe~1U~8pMU>$+{9`Sta_Hh;{vCSLeA!gY-u-VhXV#wou9E<=HkO~^V({d#{_ z&inWg)yY)*a$a`~S9(qCYi?W1WJC=NhFK!ky8ANBdaeX#b}WG=$e;$;FT&4V*t5+U zYLley`V+I(ZF&d=K0V*-MRf&72$BsM>Ko`>N>;q{pO{;7IgwUMMWpfR>UeO)$usE+ zd6to-*g_&r^FgL9!uu$AC3ej`tov(E0EJC3*Cx%3>?t;vlr-DRAkE)~i|10&=L7dnscI!EP>k}3IG$O11t82*P8gL6R#h;93q=Dayw#2zT&9*y z5NQ3a_3ncu$=$Pg;Xj-trU6e_8j@a!C8N^XJWB~8XD+SIT~1(mT_+2e{^)gTZk;x> NPWY+y^go{}sA=FG$cO*{ literal 0 HcmV?d00001 diff --git a/gitflavio/objects/46/cd427d457cecc5889a92440131ce63d7a92532 b/gitflavio/objects/46/cd427d457cecc5889a92440131ce63d7a92532 new file mode 100644 index 0000000000000000000000000000000000000000..7ea3ffb868f5a72652b470097dc908171d392dda GIT binary patch literal 259 zcmV+e0sQ`W0V^p=O;s?mG-fa~FfcPQQE)6SPAv&ZOwudJC}5CQ^~56um1V+6+&~%lwTD}8XtwK^2tw5EXmBz^GMAp zNG$@Z-pk7(Tcff&X}-wcAI}XsSeusz#X?mF6y>L7=A?p^*+uXBxHm|UC14Fx43EFMQMD)OcJN!H?m3Sn!|l@- J0|3xtcFw|ueMJBO literal 0 HcmV?d00001 diff --git a/gitflavio/objects/47/f6b90efbf694365d7831eb615591c109ab77b5 b/gitflavio/objects/47/f6b90efbf694365d7831eb615591c109ab77b5 new file mode 100644 index 0000000000000000000000000000000000000000..067d235b612007c6d6e0f63cac7779d49cbe2ea9 GIT binary patch literal 730 zcmV<00ww);0V^p=O;xZkuw*baFfcPQQE+s3^$Q8s%S~bMS6n4^Dltm_m3rHS~} zvUY{aLKQiCIQqG}`uMwp)LFG%S~fF5clig=kRN;NW`sXlX5Il)=N}a08s+Kl=Na$k z>g*aE>=@+f3er=nvGI3C>!D(PyB&plk|*cL9(}zDs>jVUBHqI@IK)3F5~Q-m<5a<$ z6>mi!uXr_oZpgtUsR2?BK$YgErV1dH0j@#5p25LDD}wckGZ=R2ez0(hc$uX1KT?f# zZSsy=(Mo|(bAwzRU3|fozde&Y@v+~wr|xSnK7O>j_Y=qMnKe*F$@#ej`NgS4dRfK! zc??`LUfmGW7nu1>ww`}qgWuyr&bJpr6{aQ^>lI`aFg)eD_3CVi<{D3(dZ{8|)|d&d zecMe8fIuOwC_k@+L2kF3VaDeKjde4Qi8ekv@qenB_ywpD8Tt9yU^7;FAKoDG?AIZs z_~IkKlj_SK-Tm?mNlkomeo;|sa!G0mSoM#H$qSqq?(`Tuj||-{bY()Iz@`5X8#D8Y zOA>Q(7%KJ5y(~SWubd0nyYc<%lJ_OEC+vWz$SP)V*_pX4NyWjo<(~0=&aDi$k{AqF zA(A=y$%#3s#SEg8eEid|DLVgEotq+?%5iY_o{gMP=Ky^Sv?5P0FBKR%AAc~&lD?q9nC_&@1F4FdTmzXz?^61OXS@KVe6W9awAucQ~ zN@h^G@yWj<-uls@dl7PF9j#kW{p68@$d;t$7UTf!D`q%+B>uI_3&lO11*_UFPp-KB Mf45#L04Ux^ycRcf_5c6? literal 0 HcmV?d00001 diff --git a/gitflavio/objects/4b/3ededa083d208e7ce6e42b8632d295735b2982 b/gitflavio/objects/4b/3ededa083d208e7ce6e42b8632d295735b2982 new file mode 100644 index 0000000000000000000000000000000000000000..2249814d4cadcc4776753229f7eaf3497e557c12 GIT binary patch literal 189 zcmV;u07CzG0iBOcN(3BWWdPF|Qk&E`x1?Nn?Ut0RFvU$__uC`1$>SixZzc1zo8cvvGDe8ZB9=Bet>?=joicTsu9E?}eCJ?(Phh#SSNF{ABw0 zheP5a=yWtXIG_bru4XE^rZZPc!8G1qdN>+Q=v6QjxM@98Z79pmoK>_%#GP~R8W({( z%X;7jFj}@H+hRvE`+*Bx#XX1M(^pZQEK31-9C?T=wyh2tXPeHSXBDt;8>X4_Wu;mK)#2 zXsCXmoPj9hmlw6OoKxYfq6ER70Y=M$;S@g~cj(k0O14&ZN$(3{hj7v$(Qew9D(x+u0fqH|Z^q-Cu2xD&c*UtR~r zY0dAWr-ZU>$$qPf0H4v|wATygo=X&=5|C%Q=xzmptNn=uqksZBce+)@BQ>=qM)sB( zSK^POF^8B`f_mrsJD%Anad&?_@9U%(iq2zch_@GGe(-h{o_iFT%kkm+oGfgC%0=($ z8kvjTGLV;|nTqr?hTxz+cXP+|=$?H!mc(I(d=lnfK1iXmm0~&e9lN zPqjiGvVrQ3$k^U!B&l@JjA&|5tFm91R@66+0@UAoto2Ui>~QtNDarzmn=ST+0!hNJ zMe^VgB5~D0^vKU!(;z0aly52AcjGi;F+2V@ z{h19NLA-@pdaNIYcEA`qID@GIUcWuQqPFt!T z)-p(e+=?54&Vd){8?fe325l1mgMg@-##g&OznIVHorWGokdb3&9Y0BF|-D% zoG%xX$13O*no#O3Xb)&LU;)ahy9%gF-f9)0=dJNr!lyj=;#j%t{SH=C8GXq7dELr9 z9esD{8t$XK%cpdKRE>{ZqkrdOHNuyF5FXvFFwrDhvJC$7JDNKPPJu1JfiHWKhLj43 z(C_t*VxW_OBFLRWNBMEvA>ZOy(EUG8Ax_+^t*K(P4JA$m-p15tOfsW(d1cqb4!ywP zVFl@(i@*n&q;&QGPcbKAP@+*EQH)Ep8r1QRpiyoo2W@T zxJ=cIh>Asrg!X>rHFe`#USsxowWng*13?+p2Iz@UzRou~2aRhJ7@$TYqKUT1jldy< z?|`Dttx%rpKB4Qk_G^LO_y6?!%zJg71~|JBv&Gl}yIzL~rh5_td)%#Tsv52Z4hU@T z3~fhy8P2)bmFat191jEhH5G&sg*mjD0& literal 0 HcmV?d00001 diff --git a/gitflavio/objects/50/5476e9934cac4c29eb6a086daf07671e01517f b/gitflavio/objects/50/5476e9934cac4c29eb6a086daf07671e01517f new file mode 100644 index 0000000000000000000000000000000000000000..4d8b9ee855c194816bebb7e85506494fb031c271 GIT binary patch literal 1461 zcmV;m1xosO0dPOi+C``n@T+CO-(d0UNrId^nSA%!LC*fW2IFxxkI1bO0e4~8-4xC{8V|Wkz6Ux5hHz>tNt>;Vp*$8Ey?Qk7@ z95PkS!WZBIdHb{%@e<|9dF@`~oqp{(`d4zfksBe^Zg)?NH{= zK(YHQlzjS;_DCipy*eP4wV&+Ab9e-lc+-Ya}k;g&eXH@T!1+quOzxg@6?8TAnN{oImE ziGM3M($3Ex$YT_WcIA?s$R#n!`4Y{Nnp_i7Kf=uGuO^N*bjwGPZKcjfdiJT&k&bbp zZI6y7sY$a$P2{U!lGs#-zGZSNjVU#;%sSHytxjybY~w_Ce5_;d2h-F+(^|@UY~Ssg zsGX?EMcZ0(QBbef)nw>eOM1>MTb+8H+9VxvQ{LJr)s3UtwA-;wSX+#QKIs3BOxIT9 z;sNog$s$xW8C#QD-6~eONH4^NH)_guOctiP?P7A`V`me_LOYnm?HaYVYizHQaCiX)<^&93Ooqp0GHMqi=?n76|bJT zt|ZT?h`vDBVSclJW#qWzj; z#MJu0!u;Xd-m&7WkW(L7Ep39{kRP9+p^gbdmzunOzBr(Rw`$@vYn^IL4LUbfMEd8|%;vCiY?%wOr32j!7@=!BwY;I9UWbP9*10d@dI7;m8v?eB3gEzp}}Sm`5w+(*;~xu{YX@F(5k!~+y5;u)8zOi zoT#eP6x)rO>-%#>!#P;|kI=8t^-8@B$!M-)umgB@RvU zd>Y#(Z7O!HMTfFbHR2*+(u90$hguLPH1GMQ*s!v~|5NOcm+lXg=@@zrZLww~@s%D1 PGig18zZ3Nzy8<@eqXX{s literal 0 HcmV?d00001 diff --git a/gitflavio/objects/52/024d4228f1a54de35513bd1a14dfaeff1ddeb9 b/gitflavio/objects/52/024d4228f1a54de35513bd1a14dfaeff1ddeb9 new file mode 100644 index 0000000000000000000000000000000000000000..1a8e8bd6ee3621c92798adcc9a925ba9e391fa70 GIT binary patch literal 339 zcmV-Z0j&Ob0V^p=O;s>4He@g~FfcPQQAo^6tk5e-EiTb3$S7cNY+kFAlICNb`7h(G zkh$~ZwM=pPFong%sU>=8`9-;4l}`8Hf4#KLRqvO?`Ja12-?tqKc{v@bGC4mlEi)aV z^fvd7Q&*onDVaa}{M6&O*m5UZzB>R_T2PdqmYI_ZR~sK6&yaUv?Q;I~D3!P#j+xTe z%KmLu-DnO~UYuG~mRbZ?&G2k@?5kbHe_zQ=-+5s634xU-^_T0SDg}AUTJ^)F=euT} z3IEF$$#d$8qWIN3DO6Q(|EE^+)t2)cn!BElrUpa@lIS!QZEH~=QBnDXiN z``&qfUnkyX{T6w)pyJ6b6s7TCQ^Da2as|WwXFp~xyU_cU|DmXW(f3QvTFQT*>cI+= lN=r)e^T0-nO9;9oz1H>C)Ly;h?crnJ=hxUR1_0B>wqjj=vUUIf literal 0 HcmV?d00001 diff --git a/gitflavio/objects/52/4523b4454b2c3bc0e88a948fd4789af7bbdae8 b/gitflavio/objects/52/4523b4454b2c3bc0e88a948fd4789af7bbdae8 new file mode 100644 index 0000000000000000000000000000000000000000..86c43aa6d82ef6e1a78bb648f3a1d46d0ae797c6 GIT binary patch literal 3722 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 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/56/eb4d8b0f5bf018d99412161cf1346e29540fe8 b/gitflavio/objects/56/eb4d8b0f5bf018d99412161cf1346e29540fe8 new file mode 100644 index 0000000000000000000000000000000000000000..ce4aafe64df25ec665e4e9c9fba082c181f076e3 GIT binary patch literal 2020 zcmV$i|Vgg;QnJ3BkC&&&?vAR5CTe;&Q~?f4{~$F0^x_<@&*PO1{=D_1?l zBJCe_U$$C@dwVU|gM(c?#Ox+{?fIXUF~q@gCPSD;3A{bO814F?!&WQw79x#3U%=ZS zmKS{9zc>PrNHkr@4!}x{Li>=unoy=! zJ{Y|?m9t(?rYnC8LrSJeCv(-0an2ohjFAf8zCk;)4$!9`la~?16<=hk(w`BL#lsv5 z#pm3q9jYp18hY3~noNd>kfAT4sRf~=(i2`k7$5|_NutHsYb_E*#P(KeYb@wtF?ax*C1_aH}!Q*CC_iLO6GDasouxasD}298BO{PxeyBR;|tU zLs&*u*=cy9^^63v1BtM8|BQN^bPFRME1={Nc)4qREl7d-a5R2E$lem z^}Ju$G%7BzWLOi+@oE;%;{Kx*_g z0s(7Cmm+Q?H*WT}X!n{LKI*41vD#V?HwuFXc*U>?^sY^n-#&~ry(IA-j`byE9~Sb9 zm<;$bV(8a*f{8$k%!*>>4b3j^8*(rxHig*+{;b;{LJ0nDg5Ii?we6wqK>Bpcq1;4a zR;ip9X$wuQu4U)_Ng~o^ps4+zN^StU5|vPl#0}Ay%#xdt0MvqFs#ekfSPfBL_KB#- z>CVp4RfcIrbP^i4ZN6JFg7K}Z;`ZVlpy@XDt3BwLTPqo{T3k14Z8o&cx!p5n(O$7r zw$hLuoZ5xN?gPQ(Do;*0i#N%X#8Ze(CPEYO-%_NiQwqp8mml9_dCyw0ywNVj*5B`j zmqYF+iyStN&Wvzfgc4DyUvkbz??=~sven!bLYea1kCvfg6kxTx(8tQtbg!a$i1sQS zSd9ycQf6~7Ft-W14Oz6-;Ag0tSJRD*F2I{iFsIUna*z+PbQ<&3U&llRuXXI0#kJsE z>Y&SqG-V?Lq@?Lixwn_GYxHpLx_n_JILUi|d!0>2 z_5D=7P|&#PI*CiSFU*BxoqMyi#|@cVEtg`C83*12&GP`8O`I1ho_&(AYu!q-NaI#C zz!eYAfOZ)lmgYKSC2Hftb8*W=pa3;;Zum7O^WoHpXm(lE$IJe zzCUMWkysaG-aJbsGJ1%!RvlaCx|u4@x%K*HGgZx%E`?OkGjm!j2%c7r%!-=+J{B~# z+96t80cT>P=6L2F6~NM_oG@k;#L&Qk3q=R6c+ye|?=k*> C|Mb)V literal 0 HcmV?d00001 diff --git a/gitflavio/objects/57/41929666cdf91205979ab26c6b6ccdf8a6979a b/gitflavio/objects/57/41929666cdf91205979ab26c6b6ccdf8a6979a new file mode 100644 index 0000000000000000000000000000000000000000..f773f1097ac5e4a6afc7613ce8d23f9283119b7a GIT binary patch literal 295 zcmV+?0oeX{0V^p=O;s>9wqP(cFfcPQQE)6SPAv&ZOwudJC}5Z^lzE{gx%B8(P0p%m zxnEM(C;HLyM9U tle0?;91Aj?b23x&O28Tt?^Z3f&D-sKimC7CrGmU))i>W|003hfiNlu5mVN*L literal 0 HcmV?d00001 diff --git a/gitflavio/objects/61/b7171b1db577c2de9d43dadd5cc1fecce774dc b/gitflavio/objects/61/b7171b1db577c2de9d43dadd5cc1fecce774dc new file mode 100644 index 0000000000000000000000000000000000000000..be422ce267dd6656a3381a74b39a94410f322f6d GIT binary patch literal 634 zcmV-=0)_o}0o9aUZ`v>vhIgG`aVAv?AO`BiR0+z~EFcuArHGH+l*%&U0A3S2vz@Bd zy8k{WvD?y)(p@e$g!AQdp7+GdL@eR>I$m!?%I>HR6=x(NkyzWHLjZEv0PYLqIQBCqU~5PQre|5v^@DDT8xcfH@9gVL{d%z;TO>4264 zKLUz449~|A=q*#Q72?Ls7o*Ww852UBONm?q)0h@pT4-JYG=nx^EhOL_O|t|IhBV7H z$|j5r;OYe>%eE+K;`E3#tc0n}g?0x4g`K?tvHXFtb{S_Gl{AHv-Qfz3${xZOP5o7C z+vWS@-f2M9%41&0eq_Nx53UU7Qf9V>+DE2Hb(T$ohX>nduh#?LuS2yr&vwVv>XucE zWQDIR=5~px$PyoLx=e6oi~mcprv;?kBAY9ewfVRo%m($M&wU}`WKe`WKh5?0wD(2R zhd1-_dnm$Nu-HM}>4fLObV`D77F~|WbTAGk!R&JKBI(UjY;Jft8V$mksWJIJcq(vh z^dh9@&*iuBB85*73(n1gffitJm`l-bg73b3-(12)r_V&$2g!2$pCI+4>H9gYm~yDr UQly7r*_+XAin)yb0IEJUE@kvTt^fc4 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/61/ccf6d74902ab1b4ed510d8dad7df2fb80ff875 b/gitflavio/objects/61/ccf6d74902ab1b4ed510d8dad7df2fb80ff875 new file mode 100644 index 0000000000000000000000000000000000000000..b4de5d762e5b1529859757963d33de39ce602716 GIT binary patch literal 656 zcmV;B0&o3z0V^p=O;s>AG-oh0FfcPQQE+s3^$Q8s%S~bMS6n4^Dltm_m3rHS~} zvUY{aLKQiCIQqG}`uMwp)LFG%S~fF5clig=kRN;NW`sXlX5Il)=N}a08s+Kl=Na$k z>g*aE>=@+f3er=nvGI3C>!D(PyB&plk|*cL9(}zDs>jVUBHqI@IK)3F5~Q-m<5a<$ z6>mi!uXr_oZpgtUsR2?BP?bThjxN4n`-TR5-_RJcnqU8MC zg8bstBE78Q{5%G(8Lw`L=?lz!CR@+Hufgx}A?Mo*p$b!zi}eaJ3K*Vp-FkJlM01U& zPQ6r-Fl)>N*S_s020);YR+OJt!XUTX%`oG0g2uWT$3z>So%lc1O#A}Wh>ZOFY_J*S zGjCVruKu3%uSj_FlMUvk3BTvHL(Is`D=taQ$zj-f=GCs1YrA?fWE(70cv=g~`6P27 zDzb_hTy|zIOHy&LZMkQ>pK~k2tt18mR)}OyesW??YB7W8Bp?6uYl_Z)Rp+M2rg9wI zy=NmQ)Db{G0IkT=%S#3N_u~%+nX;WRVvn>LyEi=0Kh^u@sXxS|g3_d%%w&cIoR^q# za~6lP9$#jB%Kd9s0RJals6j=k$tC$kVE-IjYMe5!yMJNT&bL=3_cV7uKf~V*RAp{z zs!)`gn_re1pORVtRG(Oqni8Lu33Q=eaR$Ri*Q;mp-Aw!=8J`@|+p=h3N%FbHP_v6u zOG*pCMyqt}iV|j-?#L#4cV5TsthIm6?@)!fu(&9hLFL9L|BiU;M~Ci3$dz@pZawvr qM-C!elA2qP1GKN0;qa08*Dfy<_jDGlYP&qS;`;yHdZ_?;NFq5xNKaq@ literal 0 HcmV?d00001 diff --git a/gitflavio/objects/61/dd7aa53d6ebb43ca028ef9d2706efa7bd9ee68 b/gitflavio/objects/61/dd7aa53d6ebb43ca028ef9d2706efa7bd9ee68 new file mode 100644 index 0000000000000000000000000000000000000000..242c164f35c83fd7a9caf43b0dd75ffb394b9a76 GIT binary patch literal 4038 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 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/63/ea5c4d83858585a4d32a54f12e00a4c19891cf b/gitflavio/objects/63/ea5c4d83858585a4d32a54f12e00a4c19891cf new file mode 100644 index 0000000000000000000000000000000000000000..5e6031c682351b1e9aa40a315d9a1fe50a2dc1cb GIT binary patch literal 1049 zcmV+!1m^pA0ku|bZ`w!@zR&&@Qyr2`4g^tq_rcdB$_sfpwK)Vyr4t}z>?N#X@4CC| z_DnY>3*`;GqW?#^USPgv7EvC?qA*CKAxnD)Nvvf#(`pbE)9!Q~&0z59&CM2ok2#AY^bAr@U{^ z8{ey$J%4;Ig;6q&nZimlz1wKW)Ds!(``%Y_&SSRUV13+a%FkoYgvm{Z%VaLaa(tGF zC}!hJg{eME^;o=bavPt{ke47do2_bubD5-4VBqmUW`>K0@u?8f1cpmtw~FM}CvNPo zjY%&hCQ#V2uNl*|{PtS^qoo)ix>D%kfNPV)MQTiIJbnEVsBnQ0qsX*|9MDZM!2UN( zL>gY!>3nkl*Y6`LjCpP+S4<}>80=_Ej&@r>O#9%jzfAKl)0k_6UlV2)GSWVb+^gQ5 zJ84dA;53`XJcM~BLIMlOI!{k~O&^{lkE~su2BpI1;JwRDd_N4t7tWr&W)n6RWJN&u ze#CVe2g?C}SeTwtQhC@>^qJPmDIas|1q&jkymFU})(1T>bB8^7i#2+~99rMUO%${W z;@d2lp=Yz%syH{M!`|RaZ!mKE*L{5cbm>m27H$wFT)5TE^_iBeG9b+Y%81sG5ud)V z7xEd?q4q#5h}&(eysEjBhH}dbFN*#VpPg?!TJ7hQ&fURj3l!>?E0$m@SgLvC!qG9f zT*5y$6wvWRF3m?g<1%uw{ zMS%)ox&jvC0IJHU=GQ)K!8!Wf^|lC;meB3E-l!y%0Ho#WEDW*nte)lpQdMmlYlByf4jx#pxmyV-Qwg_ou-0Oe z7JTTw-y(5q7_4RVk?O7M0p)cBgh&&$HYG4EgA+J8fyNXk+vc_V^WlFb95>=Ah%x=5 zi*7DrT#e|I$pi7 T7W~<*_7kDBa#qfFK+#hS2uTcd literal 0 HcmV?d00001 diff --git a/gitflavio/objects/64/93631fb88f9a570c95cfab46b89a144a9e9503 b/gitflavio/objects/64/93631fb88f9a570c95cfab46b89a144a9e9503 new file mode 100644 index 0000000..76b4ba9 --- /dev/null +++ b/gitflavio/objects/64/93631fb88f9a570c95cfab46b89a144a9e9503 @@ -0,0 +1,3 @@ +xK +0@] o2 eL6/ +m6^T#W+9KZ+. ) yH·lj3DHb'V Kbp*e4^D)yLΩSP;vb{E \ No newline at end of file diff --git a/gitflavio/objects/65/790e7d770de478c167d9435a4b7858346f7072 b/gitflavio/objects/65/790e7d770de478c167d9435a4b7858346f7072 new file mode 100644 index 0000000000000000000000000000000000000000..ffc3c860b861c66d38625e54955014619e5ec668 GIT binary patch literal 769 zcmV+c1OEJY0i9IaZrVT)?X$mPDj}_jLLh3SO5~>8N>S5*xTvZ~WG(i9w=uh}cb%kd z`1j7PZ9*HA2P`3*oy(p%b7rQAn!^6a*1@}@(=<-K=FW}>J1|0R;H=*Tlu@dLH1_bD z%I-Bh3KOf`K&x>#-B>Jp>J??aGg7M(|?BdI~A6#|^Y!?Fi z4g%N%*4*1;^~06FO&JV&y%Bj)KFJs8m)^T@P$y5X*@SDwBUlJ27Cdn%ybOQlbbX*? zi0;HCaTf>~HtCn-5$ZiEkQF&DVhrzB=;9Dbxv4S63$pED@O_u`yA2iYP(}4Y*O@;K6?&9E8_3)ih=g zm<1w&)Qfz!sNZohIR*knF|ICmx7ga8;!b(2rc8QO0+o6%@J1yN- z)!Y^Z`gTU$9F-xBlR27CnOa<)uXSa0^EMqRZUU(c%v3Hn41~#km6J$hSc=~QURAK_ zM}@N#evJk>Pi%@ZS>wdecDzth(!!(V=W4bTu1e0+h#T}LZr-iMO4hz#9-8zQk?y`Y zl6FkMQxRyxOraD8#CBCEr@O%b78hJ=qWIo#}fW6_A1SLdcC3@hHyJBcGC7Ygoy5i%!?jspo643H`dou=M@{71s*zH0XW< literal 0 HcmV?d00001 diff --git a/gitflavio/objects/67/9b0dafccecfb1a9fa9a341853f524b4b52abbf b/gitflavio/objects/67/9b0dafccecfb1a9fa9a341853f524b4b52abbf new file mode 100644 index 0000000000000000000000000000000000000000..89b907db06cf8bd58d31e18d49b7b384d413acf2 GIT binary patch literal 2948 zcmV-~3w!i<0o_`AZ`(!^zrXb*c!BnTaR~a}mfQ zkE3Ez+^vNi9QA(gbPo6SI&6;}Jk|$^eaK&g(amPf*2!iOr)-|(?A4p|(PJ-k*y*I< zO2~B>3HB;k$LF-(#}E0G)(7@BQm~iVYL%s@FRtGHY<{mdg~-(}ocHm$l%i1oPsKb= z<08(|hn~L;({N$PoW5Ay$xn%z^SW5+Zg^ zv))XTIAXF03%GRic(E*yM>b$QqPZNn!jYsn{IbVhiGra@8IF8LPZ?-cJ^W=I=HZIP zX~DYbl)c-mrXsJ7$wjfrQ&={S(}jL`NZ0a)=9@G^(XfRmu3*J?uwZ~>2kggQ-=DA8 z!7XUolS#lWFcd4B9JtIWVU^6ifH%Y-ta;s@tOZsyb&orM~ zbts57;2lZ7Byees1i*`>;xs8mGVk}*G_n|kK)?aSWKW>SBHsx0zg3T#qcCM)4+glg z1eh#h305g)HNF&rm2O!#F2st^&PMv1W|nEFPqQomX?9~d2?4F21$$1H_F+c6S{8bs z9sh^q>I9_dz>caM?As@_WpBd+CCU18v(o{ahlx~h*2-c|Q40tFAy~la3+*frT-}1K zXTX#i!Gi4;JWil3hKD z(iGvx1A@~SXbUCO9Qi3$px2j7xv|DnM?30oN@HqHaC;il9LC-DS|j<(rHF3W?NSs= zku!rA29Zys=zV9DCYuat*BDp{feQY6>=!Kwj4S%y)>I&thR~VX3{?u?w5>OVAY+Mr zwN};`XfSlxd2BF%)RkAArrsX~F{O7;_xQDeh|M!vh+*>4Nb9>tJ{63c*9J{PnhO=s zeAi`*=oP)B7P*OOr8novV9jU1S<7*VNfvHmnj<~n0d+O1+^%Yn*feT&f zU?h_aifLmH6h)|#Z?yZY7gq{8bgPJzGYtxH1e|%Uhhwv|NV50fvvVB!TG5L~md(Ev>mlQ1D z%7u0dk3;NK^_(7u*cG_)-6?7DTFb0EK-&p}q+Iy|j1@tzahI!`VWx>Ea+x=NTVXVRU?mgE5@c@UP(7 zU}%8KH)0V+cN91Nia`7yq7MuKe5Z5JC-u~%#cTAb|KWKT;Ud3Y8E~t3EKKx z^~Ea+=9H1C@^V_F4E_$*dAvedINS2QieseRbsa+PSPEZ?$0*69 zpbXiMp*p>GU8sh1A0(pFeSOl_lJKj>hw5YQvlV2aGdLKDQ#fa1Bl-{h-B9tdr!ThY zw`+|kVK+9}!Hhf5bp)-WPnC%HS1Yk^P4c^thKQ(u?j{z}xbv|Wd{pi}_DlIk&&SGr zfrqmVl{bCUA*Zopyn>}ZT;GDDRqbHdTwHL;z^zvuI15s7OGRN=u-h1_4I6|Hog3;| z&EVq5>%)0pQ$kWYdyFxI39RbIHXFExa~Injy6$0nCBSkLnlnbvZG-Eni|gJf<)yLe{W59do>SO>irrQ zsII%Vf2~)mlyJ>WVAZVy2Q{|LqFP+@7mGbpq4fLEbHI}G9lrWv-67nBZc-j5>iwFa z=bd-@?ya0+NxNY8Ce3bB^um4d^mE#mc?XSzdK>$4X5T|;)?rJ~Ofk$|%{x&Y@<3NN9hkqCMRL-^^<3oym0 z_z47%Lr85Ugb6rO-GgWfKu7>Y^E`^+jX5@tMFOwK&Eg!AhvbeW@s0l;UkSH36F*&f z2k#H9CRpfVw&B)07%F;W?hP@0L#HG~M*Z(@qe7)Lz30I7aj!oWzoP=5h}F8dgD~p0 zRnwF|&tT3QkFYajaM2z+ftUD@zkZ3`W**!Lg;}P2=blAjf#)$Fua@vm1v&37tnsxb zUQomnOQ!JoN8$K;q>_`c*GDB@bIeNG*c2yf0=>0~s8~Dk0Mg zlWNnoEV`{tu3ycx-ST>eO~on*>-wmwxm`{W`4dL2CE0$0UiSju)dTz~>qlV(yIO3I u-YIb6Z2&9+zE+PJE5NG*XZhD|Hlgf61K(GKHGn$gJw@Gj?tce?w?(p^QMZl& literal 0 HcmV?d00001 diff --git a/gitflavio/objects/68/22b2bcae642863ca6e0115924af954e14ccff8 b/gitflavio/objects/68/22b2bcae642863ca6e0115924af954e14ccff8 new file mode 100644 index 0000000000000000000000000000000000000000..6995682cfc95911426107e1a819c57f542583b6c GIT binary patch literal 95 zcmV-l0HFVP0V^p=O;xb8U@$Z=Ff%bxC{8UZOD&2|%P-2+%P7gs(JLv>OlJ^z$W+o< z;aymk7}2xEW|iZ-H*I@SHRNO#mmq2Qz}He4v{QM`+uk#GxIWI#c>TN9761frD&%^3 BE7Je~ literal 0 HcmV?d00001 diff --git a/gitflavio/objects/70/dff38c872847e1c23ef1c77594086839a2f985 b/gitflavio/objects/70/dff38c872847e1c23ef1c77594086839a2f985 new file mode 100644 index 0000000000000000000000000000000000000000..4df6e404a7b074f6e083aca6ea5b9a926eb7c482 GIT binary patch literal 1206 zcmV;n1WEgN0hL$HZW}ic?zvAf&?P4;LF*PpiXL340TxwkBeHukP*A(v6>Bc3aJhEv zr^&JJkr(Sn?ovZbtE9bIv-5HOzZq||-O%^vKmPDd+cm19+3Z{V(RmmCnq52lmn{8R zQJwuEU8k&hOTF8O^1k2EoB8?t&7FbTN|>e>jeJYW|5@Bb*}@txjX%42+ClE+-~RPZ z3uS($aw}XX{V%sS@6LV-y1`;Fed~UWFS83QIn<#}x(Df4bsc}xR}tO+ft1$Ns_C8Z z2%Uk5Z4f_0EWc#asb=dllS}24_i6%iY4^f0Hqy%6%WBScFJ;&DCVft!Qfal8jqnz# zC-MZ0Mj>BBv4&^}V94d(KELw8T2I=RF@7qR>`Jj@`OYa*s-4i1kry3G%qpj#BB(Ly zW-BR*NOhzP3F`XIS#n@b;rmYa4L&_8t*P%MdC0~909B2V6?q%QE_Xl6WfEV=p`8BfXm%x_zGxky`$uK9ZxNr8(3*ToGwZ?BA9R{r8@Qg)c55`FLBg24&R^Tf#qD31g z4!pFjq%wXv(k!k?RF%U_vVLJIO!)J&C44=B18oH+Wza~i$A+#9C?NAD{Z2K!s_O%V zBuE+|JJ|yVS_-?uy)w{!jw+7sF~1IhdD?Jsd(dy@f#Q_DQ7J8t_O*H6$_VkT6#Ne zTD91Gr)(<(1GbXL$w~YF?SUyLymSUE+uACt@q1edtvooZzy=Nq?DtaEqSqd@;4qig zDVgONyD18vXy;@lYh_gEom8I>vJCSvBsqhPSq2uLv)&o!N!un-#L?O|(dWFW;JdjgWMJ`y&)g?6J>Jdu+h zdnAQ$?0{pwJEyJfU6+yb>XjTE8ulnwL$Zh=4{M_ZJBTn4C8~mDcH}OBzO8q11E-uS zw+fkuMI;}T9ZXs$v&C|8UErxmm?4~LGK>mXh2b^iBHJR3rfhgWWXT#)Jo^QHqW zAKJxMmiNju^LbW5aIdOWIo8N^HhxW!qle3TT8jsq88|7hNVDv75J3%3+|AZPKZv_o zT#i9opp@9mSwT3maX}3onvDn^3xfpEaMfiabsP2pPQtOtCO`&FF#$vtM`Sz$GT@lK z;T}rJjOl>v53J%)=rJI$bTBuZknzRafgnDJSUAUyC>z2)6SfWQ42z$N~=#s-#bgR8;?OvSv UYrVCPoVE8B|96Smzu;N2a&sC@S^xk5 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/79/2e374a39495bd4ce54bdb1efab74ef749b90b8 b/gitflavio/objects/79/2e374a39495bd4ce54bdb1efab74ef749b90b8 new file mode 100644 index 0000000000000000000000000000000000000000..6006558a0ac573a240b65d3d2338ab373e7e40b2 GIT binary patch literal 115 zcmV-(0F3{50V^p=O;s>7G-5C`FfcPQQOL|IE=kPE(JRO(U~tXOTOaEs`RY#?d!D*D zUuw0f+X)i`AW+DyEH2DpP?lJFJED63I*Y$gZ{LuenR@Wf=IKylO7l<*a%z3W!CLn9 V_RRl0H*BvhFD!Cg2>?k>FU=hSG-v<- literal 0 HcmV?d00001 diff --git a/gitflavio/objects/79/32e201a26405962f2f20e3d55c57236c152f91 b/gitflavio/objects/79/32e201a26405962f2f20e3d55c57236c152f91 new file mode 100644 index 0000000000000000000000000000000000000000..b7306d12e2c9c361756fd68e7a3c3d199bb7e5f2 GIT binary patch literal 689 zcmV;i0#5yS0i{!2Z`v>v-E)4$9g38sfwN+%C3-M~~EOyGO zssDYpLj$V1J*-FwVf);B?m6eqxtzoPr%vbH;c=ejUVCrPgFWbd>3stfX)c-2u#g34 zRGNt;%!(62@5>x|qoH}(_B`J{PKHXMJm!n}azP_FY zQ3Ss|FrVsf_o@jgD+F0#m?VSYI7yoDF^UenC(kSJXUPgoq@W0OG57$DHWSo-d6}a6 zU0^#DkUZy%63wJ&|4>pK0L@6DPGim_cBUliwunED+9FQftv*1cXj>U>OIf6Sg zzcmwoG3|`(>CWGd)zFTxgFWm*=Q$|< z!*VDd2sa!mHZUCgm8qdAa1G}?XCvpy?QEmg?Zl1ZEOrWS__dCVL1%?r4Ni6_ze&`p z69{QHtrSM5m%8fWakDo*<;-OIAH1Ij=;f^M-agaZ6!i;KCASqewOU7Zx6QH$q;@;p z$kx_+;xbQ(Hu-R7+Z$`9{oQ^{V73M}w+}f{Iw3hrlqR~YlC@#5jho2@>_ZHl*MPhE ztC7!#cdsGbk{W)7lj9wHZ>?G>T*NF^K~y2E#8q}*Zo#I9xqmVXs%Zw{G%Mtq%sGOO XZ^)&OE<9hWg$g6f;mP|0KAo` literal 0 HcmV?d00001 diff --git a/gitflavio/objects/7c/48ca70eca8ed15e3a8ea9f9d54c1a465501a40 b/gitflavio/objects/7c/48ca70eca8ed15e3a8ea9f9d54c1a465501a40 new file mode 100644 index 0000000..cb3d973 --- /dev/null +++ b/gitflavio/objects/7c/48ca70eca8ed15e3a8ea9f9d54c1a465501a40 @@ -0,0 +1,2 @@ +xMQKk@Yb&PBJWu[@;ZMIWHi|i]h/ YB:T$Ў=–!qN"%AW# %UX# +guU㱴d&'{af­Ü-]wk7_S_JmsC^ 2uّrX,:+@UBJę?XF6oB+T}q`4_ۧqù MG,2Kb+"zϧ W%M;Z] %]H#o:l)hÎ NRItu< Z](rbXʂ^|3cMx";5R䜐GA?cԚ \ No newline at end of file diff --git a/gitflavio/objects/7d/28b1fb6885c2730f3eb871bc63936c1dc5ebb2 b/gitflavio/objects/7d/28b1fb6885c2730f3eb871bc63936c1dc5ebb2 new file mode 100644 index 0000000000000000000000000000000000000000..e3f8dd522bcc6f3cbcefa2c9f1ed4391b9b01247 GIT binary patch literal 1003 zcmVx(|Dq55m;On=q{F>vRm83(`Nfyz+{ig|&P?!pXj-<1^K`Kg17I-XK;KRMF$6P9K#rG!KMUnNFf}~1favbB8wnRfnbgCM^Tn# zQ{fhot7Zn|!mKoSbp9m#fMh9bvRbW@T$PlRs>(^bq_R^Xud6a~Rwfg)OSEyhDEAIA zwr0*HJ}Jg8f^WLSk@WmZPl_~h4*jK=?j2-d%=O;RoVzOb_MTg#ed3#f*>>&EEPhd_7weKN&|uc z!w_UhdvAaOj%%Pk1JOrS0%jx^B&-O)M-WO4NE83V2k@z4d^X`vq9J7eg<{5V51F#> zzUR`jqt0RjX%Crv2Be1!H*s>GO$USf&emQ2>@dvkS23JdX4wwzsYSszJ^bg-KX}@U zQlS^#tgplb;H^;#Z99tnhtReRhJhnm6PVlSCogEFIywHzH;GSKZ&K7NK2&$R~i>uS&!60hbf?uW@bd{fW!=t|tc3`>iW zk#H3K*wMH$;F?%6fW(}v**sACW9)6Q;52C$7-wl1Ru{7vgh9VBWm$0%Ji3)o1&=TG z@ofRa`C|zhB59f&cRUj9;JQI%yL;`Eux{^;r3&s&2gV?pHF<(X!-~aaZ z>^yyUaq;uy!=M38T8bP8A3qGbjbrc;Uc=yCTn%2;$w}`OvfXX^)ut(+4H`)K>ll7V zOLLN;{L_BzYBY^iu2!^qNqe^5wEBsWvq#1yTB4pAn@mY3{31*Z8iW?=*5T$6O2=y& z%4--7=XA4v@o!i(j7K;tN@LlV#dg?jgdiDPRw%l=6X2_$x~_sOX<3T&3~y?_XP0xM znQN9t8@>b+WZe`E}%F@7F=j9^00M=?;^Nd22G(_}+uqp3+T2^CSF_>lp6BZ)E!_{1 zPtMOv%S>mmve+|Y<<8EZb24JroLr{hG^gw9azg_HGZPbqf};Gi%$!ucjFQ|Oy^?aE zVu|Io&4IHQzxbOazHp1z>Eu0C0p}s66{i-Jr4}({C~exaE=42xR34+~B(I+#4}H%6 J004_qK%DYSP4oZ& literal 0 HcmV?d00001 diff --git a/gitflavio/objects/88/1b6ebdd497616c0eaf0688fdb79edc758a396b b/gitflavio/objects/88/1b6ebdd497616c0eaf0688fdb79edc758a396b new file mode 100644 index 0000000000000000000000000000000000000000..75560ee36c0a64eb3426c9a5ba441b9c7792301b GIT binary patch literal 359 zcmV-t0hs=H0V^p=O;s>4uw*baFfcPQQE)6SPAv&ZOwudJC}3EzS}^do-VFCHY2}#( zDXDS{ZIhEs41hqvIX^cyF)xMTsIUt@c6Q-a)JP!_3JmDay~uNiAZyrZ3i2wc&|gxY4u|jSlWEQf*?a zP@{bElM_oa^Yc7Xa|%+6z(z8jG-THece|(Z%*fV$vRG>5-G5Zh?+6hv`>^lDlT$FG1B&v~GILVF%5L1Av`=?>^OcJA>o0Iy#eEQA zjgN+!8Jt>F2DSNOh{FWoi&MN5g32SxGs&wBF?Z9L91dE01> z(xwJ=n5^c@Qwo=MJT8Q(tL%Fs_6NJtTItZ+4mL}e!ho+=4_hV*_5`yTTy_Rdyq>~k z59C{EBnZrH7~iGq&1aq_i}~%RH0EDd3r9tn*{IY?+7X4J(Ynm6v-5w*QsN8v$-5-& zEC*gZzuBYxpw)4?2r0pDAphg;^B*uPXh`i9oh(a%RbjTwT@DR_5a1kAy=Zrc4clVY zzuR3f{nwwC&Xci#(1MdkcL(9tccW>$E$Pnm1_-t@G6(g-W%ld~NV2-Y*P6|CrWH@x Sle6-GrBapvqscFuQ}LVVI^cEy literal 0 HcmV?d00001 diff --git a/gitflavio/objects/98/dc9fafeb52a5c6d0130be29d5716133e086821 b/gitflavio/objects/98/dc9fafeb52a5c6d0130be29d5716133e086821 new file mode 100644 index 0000000000000000000000000000000000000000..a4bce53757d197cb6a37485af1014b6dfc3ab716 GIT binary patch literal 154 zcmV;L0A>Gp0hNwH3c@fD06pgwdlw|#ZJG^;e@S*Xw9q!C#{Y|g5AZU~WniRj+tLAX zy!6uyNQ0G3#KjX+mMl?k{!6gU#5YBs5mRIJ{r zq^d5z`rJ)d|!fFZ^>hAH9*SC8I1`ObCwGjm(hr7kiXXohy!E=|q7 I0fx{?E@pg9Q~&?~ literal 0 HcmV?d00001 diff --git a/gitflavio/objects/9a/73f51de5135da21bbc52ca183bf1e9a48cd0f2 b/gitflavio/objects/9a/73f51de5135da21bbc52ca183bf1e9a48cd0f2 new file mode 100644 index 0000000..43328fc --- /dev/null +++ b/gitflavio/objects/9a/73f51de5135da21bbc52ca183bf1e9a48cd0f2 @@ -0,0 +1,2 @@ +xA +0E]d&m2)wIf&ThRE{|iz}< "Ԩ*JT|khu=mBHxxqRpBcP^esVh%44_Fw*V^m`H}tyX7yy9L{@`&^GUHj}Su zaGNENMA?eRuoM!`FGhpscGPLLGR5FLO8Cc-arN=75Vz{p=Cyr0lXC;;V_(EezG^-n zNwMS+YZjO=`Gv`+&U;F+v{~qqD#gFBUXXGT167d2bSNvwuUf4ITk@E-$>?HsKDhE{ z+WhA!tMAWVYvHFYJHZ{B&u(|c()VnB z%4M5mmI7$T__Vwq#60CR;(s$o)=)FZL#k@F{vlbK$r!!}zQFID)-sFpTg$2KbJX{p z?e17n-2x%8#snk zX2?sU-9c>n*@iJ&ag{RZz)QXgQw3Oa`m#Z#G+t1-0L$yKJ_dD%Dr*cew=oSlYABHG zbH*iGKq_FtRT9y=qLK^4X~+t@#>@UbBzfJyY~ajxjCt#~+m#lmL7>{2OWJ!*{NlJD zVQIAUD(!Xy{mqFML7xh^8cu;O7C<4$R4Uex(m7x)S$_^hEepN=1tQe3hM1Q~hZ;(e ztwN`l=LgRquXDp72iY05k(qu|N4|--0{!7AoeLwn10VAHz@(6f$G0m4miXyi!W4ny z6LbALg~&n#*_y?vlcm2yWHYaW}7Rr71`-8^P~HaYVzdy~oF`pob3 zr`TERZb~Isjnoz}BJ=x`NnNdkF3IK*4?2NT9oIBG{6g}ZeH5NH{%7yHH|dWDqkG-q zzK>5O7oWb%!)Q2}`h%;%J>w)XWZ(2TKM^X`oe!@I+qZ3{BYcUSho_)hC{_!0sqg5Z zE)N@{wJz$Kf$JcKwo^JcO{C;KUo~(zD?LXDCOaESsgLQJ5nV{;!K(HBHgVgK9=$GDvQ?^O>&GghR>Pn#sn9G9 zg>SU+UJtsAw8WF{wyr|jOoFc%_vkW-ETexPmn6(gEoM)8}Wm)^A; z1p~28sAO$DZ{6O9Yffz;+~gC;QL!%RoU$Mkpc15fx@;TkF4LN!W&_A;+-(6WADt)1 z@Qd=An5%6xVc0lvqeu(eidoV~;Vz@!U~=`xe~8r)0m#Yo#Cr6@p;`$4Kh)9N%{Grs z!ZP#WOKiL^v;n9I_**N)HIK>Kwb{|9v)y#(i z6@3v0%>AYT4@k#~yHC5@l#*W`sa63yf(RJyz*siL1=vus_?d#)82Zt0Hi}ONv=Oa@ J^e>^`+h4SfwYC5N literal 0 HcmV?d00001 diff --git a/gitflavio/objects/9b/419ab1e2420866e6abbb7bde1e53713f021514 b/gitflavio/objects/9b/419ab1e2420866e6abbb7bde1e53713f021514 new file mode 100644 index 0000000000000000000000000000000000000000..cf6d8b78a8c42965497d3d2ed20e6a348741ef06 GIT binary patch literal 394 zcmV;50d@X(0V^p=O;s>4G+{6_FfcPQQE>Kf^mBLh@psqDO<}NVyR>X(g6{GUq9H%_ z*3Afiw9LE%sxHXY(Z$ylr0DIL$hLW;# z1sSbNW-gpnA8}^cCcZ5xT0u}VlJj#5@{3c8^sEOY2A7?g z%aT+aY+LRb@8{gga4U(yfE6N{lb@WJlUmFmI?2aB{hFflU)8xOvZ)*gckkKA2~kl{ znv|27%z-WypOi&~UiC)AAYAmBpzgr3GMT-WAT%GfFoSu&cLF^>4A} o?hMoygP2lWl+56E)~VLD=FQQLS(98C4bLTCU#V&Y0853WWEy46=>Px# literal 0 HcmV?d00001 diff --git a/gitflavio/objects/9c/730d7a658305cc598ea196667e6dadc80389b3 b/gitflavio/objects/9c/730d7a658305cc598ea196667e6dadc80389b3 new file mode 100644 index 0000000000000000000000000000000000000000..522511cf7fcb645f169dd4cdd496aec7d4e76da1 GIT binary patch literal 1515 zcmV@LY{k`s<+1Y{4bpNJb zRlRy&^|}!!4Sn#b1Bca5Th_g!xb|}I;C}T6rH;ZMZ{)%C?M+H;hTp+$@M^_B;dfo@4|89pTPUzUr_elaEns>)HWVs&kiX2?15Y0 zGmxoj7QP4<;q&k_cq`n1lI?Ii6#u=7$DsK0EEIjMN`4N$!23lge*6L@PX9oO^H!9K z-rYQ8{2+W1PC>CRfuipLecXnl>B{N zjbDKh-=EpA|53^RtKPTYQOb8gO1ZeiE-7cJqg{n$_3;pY z#ZS4!zMb5nSN7~*nm@a_r4F9pmOATSxUC-JX1l^q@kuU;$sTUWy~~4{y^cIP+}{W)SO%rGs%&vr4il$clB`ZsC;Vn7@3DQi z>)Rkz6K&J%M7C9{)zm~BH9LCVUotvNbY{|Q$V?eEHq-Sv?FWG~DQinfoU{i2t=~0$ zxmbXIYNB*iO*rFcMmNhd{<1FFsCV^2ik?okE zZ^d)TP)sCXVbsbU>0_YpVKrC#gA7S#vbI^99i4;-qg4Bn+uHRs#KqnJ;7yBU>7^@_xg(o?E#TVwiM`Y3UZm>UmcBd8p@m zCTg}hCVO?>E%XgJav?AJ2_}_I!>iAE<3$(gw+oN3W=SWB*{hs0)Jyt-7V{-G13gl* z{Zh~*BQvbGQrkB0U9e=_1+mIT1Y*#ahFXbN#UNh!oS3q5!5JE}Ri`l|%~n3LK}5Lq zBxfb6TFb~#U7Cz{qHH^L+bNrj*s*rm$k52LqSXvSFladR8(KbIckIfgUPu?JvZkgD z$DhvA52Nl>ohLFbD@F%_={tG-L`$ktY%OXf)r71n5K_z@*JxovWz02QXvSn0=-Fi~ z$}TjhCn>%v*p`fBWXnlvsKlZb;pU`YS)m&ylq_dDWrpNfoOOCp+VDVQO-~NO!8vnl z>z6*`0`X?qh^87}3!`U>G|R~f@VG3qw45m=9GKOQOh0IBE@$J6@Ax3fv9o+qC<&>w zjD{NMj&H4Q&^{@08q|D-kaH3EIfvmooywc8GPSl-anktuwKKjRY+(m|s4`^q-Otly Rza5W?hu+I>^*=QuOYe{F2Mz!L literal 0 HcmV?d00001 diff --git a/gitflavio/objects/a0/09d2026d6ca35505c7a633ca47f58a500ff22d b/gitflavio/objects/a0/09d2026d6ca35505c7a633ca47f58a500ff22d new file mode 100644 index 0000000000000000000000000000000000000000..96363d825bc9a5fbec39da90ec251da29c581033 GIT binary patch literal 71 zcmV-N0J#5n0V^p=O;s?pWH2!R0)^z_Vum&6yXQ@6ZnTa6xV+Xp&%e?(WpgJ)HmjJy dWoPEHBoznSmV3tgIkz(0N@6fz1pqWn7*v2sAJPB- literal 0 HcmV?d00001 diff --git a/gitflavio/objects/a0/e5cf00708a67b4a8c24095983f946518631d86 b/gitflavio/objects/a0/e5cf00708a67b4a8c24095983f946518631d86 new file mode 100644 index 0000000..735a393 --- /dev/null +++ b/gitflavio/objects/a0/e5cf00708a67b4a8c24095983f946518631d86 @@ -0,0 +1 @@ +xTMo@ٿbF!(QON")!Z8àWYvHƆPnٓy͛Y'L$poy{SF$JK'Q.kQǁ IM93ka%$~>ylP$E x&?\KJ۩Q'Vt}}2>bE Jm|ca|'-{Ck6IuVlrx"|8h*xcu~'*TQVRd7E)+x?Rq__{O=vv z)@~9z>9&RtOPb4>Ge_fGnVhUYuRp$HrLi@s!B-jlAQn>&IJ6LL=q#hpuO0osEd5BfvTa|Q7j7g@oDg{+j%aAG!?+96~bTd#q=!17z9)+3porA3WFe?5oH?@S z`W{BQ2mJQ5U->p$Uvw&3GcN*5L;S8oDB_&$Xo!$x`GSa~DM`MXx@l1>$sB~}Hzo)j ze^9^q!4bI5==I#79Q-ZKgocPA{4=Q$Y%c=Tcm>5SH_w!4UPDSz_~3gsACZhmJKm2c zO7>i}XtJgQ10(u@`^RYdgxE+Q6$ILW7D(2jo2RmebBpNcK3h;AboL;G{ogM2zQeuz z1#Y6*TlsE-oh@@#@E9j({zA=HuC_RxC{gN@$rY-K56fHcjUENy+Ps!HB!~95jo=<= zjFWp!@b{syvgY=1<>rLI<0$*V_tVKT%Nbh@Fs2=x-n?^`a1{p8;K)`uu*cU^T~|^F m(6Ov+s~VXV&X$=syRbiM8DYQfF30ZxTZNCv=I#$f3z^r0-WALM literal 0 HcmV?d00001 diff --git a/gitflavio/objects/a8/ab1151db2e98478a1b23997064651e00869362 b/gitflavio/objects/a8/ab1151db2e98478a1b23997064651e00869362 new file mode 100644 index 0000000000000000000000000000000000000000..52835529d05679f36a513926bcf5621417980a58 GIT binary patch literal 7480 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 CF>i7J literal 0 HcmV?d00001 diff --git a/gitflavio/objects/a9/0e9d8c9a116fe031f668b5eb45aab856b51f1a b/gitflavio/objects/a9/0e9d8c9a116fe031f668b5eb45aab856b51f1a new file mode 100644 index 0000000000000000000000000000000000000000..336f0ffd1df4f15207d0f30127f93ec5f0cf6088 GIT binary patch literal 360 zcmV-u0hj)G0V^p=O;s>4uw*baFfcPQQE)6SPAv&ZOwudJC}7z6WZ6XyQ==V+o8Ri$ zy{djzv(ir1!~h5sobz*Y6Z29SPW}};>gbj;C(6-dUFoI;7r)fIaoYW$QH5R?0-{LkFEUvcbHV&%(bHhyU zF4QQW{N%)v%=|o$)SQCUBCwH+Ck@$k!`<$wJTtPjpDdOd+vx59RqdNtT%1{!>X;1D z0#+=%@UL@#a`zvV^E*NW%s%XU@#GXMRB=F2ep+TuDp=W#+mrU`PH(v-gAD%nN*33C}@+q2Pj)JKvkp`5SP8QRpcZFyaqe7ovPKk z|2{jmQ)o-+Bwm&mz~}bmJKs4bN?8P-PfoskIQ^EbGS5qJoFa~^LUIzzP(EZ>bU)QP zp4Y0^J*dO+p&p%V&cCDhHqRhS^Ch9MU>wdb#=)Tr>3p*l5wIXBhL}-_2o)e#7}kg; zNHYFV6m~#XjNOW+3e%Cn<#;ri`NLUT|I+w?<6k-9SXlos=TY$@p-L-JnpSI|^1iU< zw6yC~l0k$a!3#oh0xQh1E_qOjTb`E-4CiS^#s$aMa|`vl$LNAAJ3H-T&NN-S9j2Jy zVZP7q3xVZsr%NORxx;=e38R1n7?=*_mRYIL5+5=eV+g~3FbP9wLZxMib)QY8=1OP3 zd)W~i;A9?{K7tZto+*J~+e@d3i4i~okr9qNKjT>DoI=D{s(RV-7CAMqW+eWKQrgD( zw+HI6;X9nejI$UEQB?!^ryR+-Q`NySmh4n(??G9*zmD@9ON$WHl2NiKC(N;iI7bhs zgvti!1w@x$-{x?MWoT1rghjZwMdVe%s%F;6z1=SS8|AT8`HOQS?j6U4reE0JQ0|uR z&1UoZX5l(w)@B?46{0B#3aHsTQZF&RoRm;vtmeinu{h@(Qwc=kwO-kJjtqh7MeD^{ z#rJ5PspZ#W>nxX;H(;y?HeWC&nc6-mbt(=dBuwR|Ou9mVU~3kEML%&YUBp?2I9kJ+ z+^bD?RAA^m1Er8?Fx$PhDBsuaEdZiglzG=p# z-e@=+^kyo@7F=j9{FfcPQQE)6SPAzfH&nqd)&&f$G(ksX)V6d|B6`ObT zRD#_Up1h~UtVa|$((gl+168JFrlYA}@bo-GL09^g6^9(A&aj`7Dv>PP22~%NT2z)= ggr+|ERg7 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/b0/0c568aa35f2f52dc581f4c17fb444d02c3410a b/gitflavio/objects/b0/0c568aa35f2f52dc581f4c17fb444d02c3410a new file mode 100644 index 0000000000000000000000000000000000000000..768ed6378286ebf053ea4e875d948ef74bd982ed GIT binary patch literal 56 zcmV-80LTA$0V^p=O;s?qWH2-^Ff%bxC@o4#OwKMX(90@jc=BYc_?*P_&gZx92AytO Oy6-I491#GG%M+nrX&83^ literal 0 HcmV?d00001 diff --git a/gitflavio/objects/b1/45d5cc6f46344f5901e4c22eb4a2a17463cea3 b/gitflavio/objects/b1/45d5cc6f46344f5901e4c22eb4a2a17463cea3 new file mode 100644 index 0000000000000000000000000000000000000000..854f92d2ca0ed984d71a9ef58bd21c3dd78c10b8 GIT binary patch literal 239 zcmV)VU{rM7{zHMwv0@P-3V2rVW-X9~99g{f#T)Pngm-x_>~n8CBalYzwZ3>YJr>O6|GK|P7Xwns8{z7EG_C}uimqio)V`$- pqgC+21@GXaM_4NY;u*%gOyd1Cc%~EFFM|<|5hX9>{s7X8-JRA6b*=ya literal 0 HcmV?d00001 diff --git a/gitflavio/objects/b3/66f2ae913116428c52594f39f6985fdede6283 b/gitflavio/objects/b3/66f2ae913116428c52594f39f6985fdede6283 new file mode 100644 index 0000000000000000000000000000000000000000..a18202d9ffce14cc6e5e7e9a9fa3b2024e44dac1 GIT binary patch literal 701 zcmV;u0z&?zzq~)2dN1cJ= z9Co`7bYcIa9s{-z-$`^^ z21;>QBE*DCtVUL=^eU}qMQMdP|>VeZiP24JV#ZxO8Zq;Xqt5@P!&QW2|*rCC{sN!US_MW zTG~S=DOgpha@?kID^#I{YIpk8cIUSP`mu;t<%e0Ql9k|Hl;=h4 z6%n>_ZwAhvW;D;L*{y}iDrJF`2W|u!v&zEC%!Bc{K3{~o1=T!6g%HRTP=%S9e$$dl zKz-0|IiYw@vRu!R$FsapD0<*)fvXo3G}lHZX)`|nS9KvEu+>)J%bysjddF!_ z1j!(y_j)MyN)FyLQksb&v)yaU@_p^zA|TsknP+X^v%} z)G<7FrFgTENqJhW=DhK}A^o;9n|Uzl2XMV5b-(Xjxbu1Fdck-)3Fjlnu_H5Iu zRqS4GI+={TKx52)j5Yu_x?f?~ZZ-dweRJV6Z7_4+YB}_s)#^d3zOq~T=Vq8bY055< jtd9SaNN3~u`*n(`tffi>&)ycx-i)Sxc^noNN=x$~)Fg$y& z%la5T)~;#`s}s2-mE<{d$QT&|xLj1xZz*#*3*=~iXxFi903hbFW%dXu;wBQJoB=p( z7;+IP7fDVG-PdjIw$ol|OnZI9cbiP@HjnlBq%R98VMG@L+LQCn8GbG<{a0*`dg$u( aSLaY$$FZnUd)c_p-)Z|4ZRP>hmQ?Crdr)cs literal 0 HcmV?d00001 diff --git a/gitflavio/objects/b9/2df0384658e96222ff592605ad63b8da5b2251 b/gitflavio/objects/b9/2df0384658e96222ff592605ad63b8da5b2251 new file mode 100644 index 0000000000000000000000000000000000000000..1da425b4fbb8de413283529ae94768e7cf0a1333 GIT binary patch literal 346 zcmV-g0j2(U0j-mOKsJSE!%0z+v7A1 zgm$QC>dDrfzdQT>Khq|I(dg#-Y;aC9RnUxiNd|DQ){rZWV9_m=_sSF=a^v9PY5E9O zmvdDRIDJF6LYG?EA2oHRe&I=2&n$k?3MV|ok2(o);ys3Umi2~7V-~$3v6s?r zC~=~%Xf@*qEg32mByi&#@_+*3fNH%EmBR-N^Fw2;F|(iBbrJZyg&?sY5LcrZdj#cx zoZ9v~RgTl;ACO!Azf+Q{)+tPDsCB-vlkaVpt7c}6QKwxit2K%gUQ7i{2oT6Mb9mu{ sQ#{PVmqmX!x`bfH$=2oo!%b(u4&04&47D5D^T%#NSMCY<257FL&^mghrT_o{ literal 0 HcmV?d00001 diff --git a/gitflavio/objects/b9/e4a6d1083532b8c383ed2e3eea7be67ca93e1d b/gitflavio/objects/b9/e4a6d1083532b8c383ed2e3eea7be67ca93e1d new file mode 100644 index 0000000000000000000000000000000000000000..5362dd64ba09b7bef90cb26e288cea769d3ff9b0 GIT binary patch literal 7404 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 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/ba/3596df07d580af1ec86456251d1fa22a438c78 b/gitflavio/objects/ba/3596df07d580af1ec86456251d1fa22a438c78 new file mode 100644 index 0000000000000000000000000000000000000000..9c59feaf718b6f313661eafa83878bebf2b777fa GIT binary patch literal 880 zcmV-$1CRW80ku|3Z`&{o-g&=*=sj7t_AqqVAx+RBSg;mNyVHO{v1Lb#Ev0(2>%Si@ zo0j57&|&M%CclrQC_c0))CSJZ-k-fGO)gS6IeCj8#_H~V<{8OOT08zKqjz% z>a`QRjsnQSmBWECNbPHg6B`vJ?zE$B4-sU;TR&7$bq=VAwZwg?C63TirO~~1#Dre( zjJd)>Dl|5r(t<}h9aIwYmM2vw)Y*(kT{+>KqAye)fe(a)T;L)Jm6B;ywzbl0)0be$ zZZ*7{@=Gfh*kJWC*l>7o+~N2~NwpQIG~4wcQz+RpX#U_ZQGl-&TofP9VXK@r(X@R< z7j%l*%@WJskHYEzG|s3pM${wh>#v~?I?PHn!pd+J@pP0)YGSYb6APTg34o6_++l$_ zq$3`#t}r62Gy5l0?06hOHs)KN?1apx)998Cge|fV$qv@+5g!Kl2;jy>X$}En#U6u^ zwPtSGdI;);HEtr|iR_(cWLM z$XBPJB)$col=in#=ec=&_0t{Q)jt`bNUEh4KL+TZ^ecw9-Q5cZLPD;$>M7*K|NQ|r GTy!2vx~}m6 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/bc/c2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 b/gitflavio/objects/bc/c2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 new file mode 100644 index 0000000..622a107 --- /dev/null +++ b/gitflavio/objects/bc/c2b35da1d4bbc24fd1f1d15c3899ef9a469bd5 @@ -0,0 +1,2 @@ +xK +0 )/V,JU!ܿ!]ټab]A{M9Z7(BٙȓIX\ZA&[!,̔h4I:hRsmVVR_鏋^00OzLUuVlW8sڏӤMJ \ No newline at end of file diff --git a/gitflavio/objects/c2/5da0a8ba00702846b95750dfb1bc8213891515 b/gitflavio/objects/c2/5da0a8ba00702846b95750dfb1bc8213891515 new file mode 100644 index 0000000000000000000000000000000000000000..15cb258a207638a0e5f8da5d046449b1dcd6c02a GIT binary patch literal 1400 zcmV-;1&8{00hLzGjv7Z0&Uv1qq&*mr0RwAijgm+kjGa*kkY(40T$pBPpyQe8NlyZ`B5s#T;{Gq}57vLOVf`piQ3q zrgS1{tw<%A6;UMIhgOUBDpM;JNfPvCGWR<)c_NL`MpCRZYw#xB{XO)&APBrM9}H z7Z**>f6!>B^*gp{3|=%{NQ}owjRD{q7YD5B*Kd4dqyZt#?XZB!k&|LSPnu0AYNqko1gcTj({ zGPBDP*)D>uVdRu083kTt;4BKauqFqEjUX3!P3JS5)yaEpWHz5I{Ga^Ubj&aZ-Mui~ z+uPgjHcHhem=w6XquKO&JiWP|@!j6}IYQX(q&s?jGi9$ny&Avvr~Wm&_XwR=RgbGv z^SaRt?j6=Cr|1;6JT)j$c-#Rn3f}=vY2+_C8WI?u`l=r*Y(sERZP|$7na-xfY5L<5 zp-+%6x%;P1uXdUzb&gMJ;}AvkKd>QTSf?w>awF2`{) zmHlAQ9z6eu{Z`r~gbb|wIO;#(7_D|0S_p|Ni)kSg6Z$r!be>t;!Q85BIKSm<8PX2PvS_>(r$}bm}A)lD`*`TBF0+2?op)Gz0)RNa|^*-g8DN(XmP( zo2*K5^4e8VbqCRcRddk zV=>Yc8p;2ts0bkV-k(gCZ^y5A!~{;c))I30MlO`n^uC|Dli_hf_?uA9JXy zrp#qFa1W zWIO(uH==Jqnr zTS#a=9Alqb-#^bcTvH|(Z*W5>_T)%#s!{e;0?K}K*v;HhQO=3-?4EF9X()D|gWSOL zv3Bj1#7-Fs!|R47ky{;O?+DA*0QET@0)C{eg)M-qH3?heVcaQr9-r(#dzox``zrl| zvxPs0)>K=p5J>b9FhJrEA!3sQItL9tS%c}}Tq`LB?%)!(s6ybD8%*i1`vIl--M^&b zRLL!K0;?QApgy77bF5~ZH$&t8f-+8H5Od`gT_&)3WBmho2^#XiD8h2j>VY GnI-i2)fe?_!|GC8N1aPws> zY_S=(=fY==MRO(#BoI*z3|;}}4?~r3D9jqI4?r1eP+Df2tTXQyVqjuN$~Ur~igKG& za@Ur{&!qrQE=UVf0|&$~i3FShy$>w}cTy>6tN@2+ZE>TiTxjeXXZ$e{(9!oq%@YE8 z(kbHGC6P#x&;@pCuRX~@pe~@Xf0WP*EdSXI-VZ!g5U#pf|uxZ|~!dNI-n z(eiPX;43bbZ~VEbx2Xg($3sk!&(#jZQp9vqSR#mB=~h-C-#OPTH(H}u9875^0^KFs zgaKW4qh|Jyh4I{=#}Z26mZm`QH`XPOB!k#tIn zpAg}jXJMeU5RxaNrqgie$6cja;~b8*P#~zHrNr6`1=2Q95@%-SG~myqs%6RR`)){I zhejJ|g5Z2SdOD?|-+s48qYtnCifY+v-c=kKBw|qqP66))_ojiu_#JG?S2%A(T}m7* zM5gik(|+2w>%E8b1no$H1CcavPyzeIyy|9l(ItJonK|wnJCgkWu#bHW%Wmkmv5iEq9GFK~giqc?jtjI2h3V6I-elCF6Vi#jgrRC5QB o(b|qe9q@6jq|nX_!+xQ1Z^$}r8}hzM$yPDPYHzoKe^H%&KhGhg2mk;8 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/c3/c45feb44e821bc8970aa86d39378d7ffbb2e65 b/gitflavio/objects/c3/c45feb44e821bc8970aa86d39378d7ffbb2e65 new file mode 100644 index 0000000000000000000000000000000000000000..99fbb480088f326b29e09fefcd4dc122b276f359 GIT binary patch literal 108 zcmV-y0F(cC0V^p=O;xb8U@$QN0)@ol;?xob)^)4f-q^(2+*_kpv*GNX=j$ge-4BsZ z&d*EBOlPpN*fV40&d#56GGf=9T&CbOr|au-h=StOqO#N?h76@md)B3BB%jJ-6rJSt OGvuMq`5yrH?k$rxpgX?+ literal 0 HcmV?d00001 diff --git a/gitflavio/objects/c4/99fd993f6fd97f81f66884726008ad9d58b612 b/gitflavio/objects/c4/99fd993f6fd97f81f66884726008ad9d58b612 new file mode 100644 index 0000000000000000000000000000000000000000..b322106a4ed2a2e8009249f53653a8074a5c5fc1 GIT binary patch literal 753 zcmVaAma;6>Mz{wo!; zzVE*E0!2~0zmJQauM2~};wq_AiBa;e)Y~o`k6es?gcP(a+u0$KM@fpjF$Y zWiu0Wmwylq`LVZdM);#;<{dC~{y{;mQJ(&Op7DOJ&aT11jzONTAU(Aj8-HiC9xCRy z+fleDd2){I(bt=xdfYrC;ypZrL;QmxK`LuJP8Gaa@mBQlidXaJh8$dy8X)BWRB3K% zssK_M;2PxX85|6>B3Q3DgJGxc2Mf1|mq|+hBh^^fChxcvtrQ3~*Vok-YNRt`|Ct?G zQcJdFg)H!xed1qSnx`04QIM;ni!a!PZ_gx8eC)UFsr%ZCk00&s{lsy5W{rsf5GW*O zB~~!lx^JES=F>|X2Zgh*CvvgvoX_;m5^6?rer`d2acYrXR&jnF1J{gKH^lS>WjsW@rXhoi0UMkSPAAc~&lOz$8(puS$#*mHk7RsuNN>xcg(b=77DKHsPAw@d00)Bn zbO8pfg&A*4CvEMXuRBlP#PBi?#O~svWQGULzZ#bvZvWGD)Z@h-%h}DQ*BjqMWJ^+W j3vz(=6*JTy;Z$WQ4ZMBqvq0v0XAX}$ZQ7dvUGhm=msxLf literal 0 HcmV?d00001 diff --git a/gitflavio/objects/c6/a533649e8b8fa17ab9edd519bc838be7cc0f8b b/gitflavio/objects/c6/a533649e8b8fa17ab9edd519bc838be7cc0f8b new file mode 100644 index 0000000000000000000000000000000000000000..775128ff2d82b53e408ec710fffdd38ab847ef14 GIT binary patch literal 586 zcmV-Q0=4~k0hLs3kJ3O8_A|d?KA0SrAb5A`U3$KRP|TqSSc;KYvst#&W6ZMK>~4u* z{O@il++nV2Y|^B&^Rn~I^Gp{~FW~I_`T3)lZ}Mf{Xrw44H`uh!kZ{axaXt0_(UV0a>AhBdwr$JOg-acJrww zavH!|Qwl#C0MO~c1dY(f5~Lis;*z#oKj_l=TiTP?Id@Cj{8D<#hp})FF$Qfo>10ML zchahJStBaP*ODxs=dXIGhu*N?4OtoT(ADoo z!#Ruk@hlqcybm$j90g@0mKEH+Q2yC|ji3%Frt=SP$D=uW@vK$Ovd32o;y?;6pMqZn ziv+fSeF3;UaI!zR1AQn{55CfKP4H+MN3&i$j@V%IuHTCX<9g&A4#@d2&jG}gmkQf@ zRuP2u!c`6-CGh)gZJq8+A+hG%d$v0%t~=d4XYbJ{lp(G| YToku=iS6$zdE&eN>6fDM3sb|KMT>kTy8r+H literal 0 HcmV?d00001 diff --git a/gitflavio/objects/c9/fd16c541466c9c5a4148ae75b2a0d1f47f466e b/gitflavio/objects/c9/fd16c541466c9c5a4148ae75b2a0d1f47f466e new file mode 100644 index 0000000000000000000000000000000000000000..28194ddbac296530b203b68cf7c7077cb2578711 GIT binary patch literal 63 zcmV-F0Korv0V^p=O;s>4V=y!@Ff%bx2uUq2aV*Gm&d<$F%uCTL$S7cVz9VS*6%m;@ V#|hh7jBh+V8-vLK0#U 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 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/d6/57b40685bcee4c0783c793d487e8c1bab223e0 b/gitflavio/objects/d6/57b40685bcee4c0783c793d487e8c1bab223e0 new file mode 100644 index 0000000000000000000000000000000000000000..3de635538482f50a40058666b39cafc26b919541 GIT binary patch literal 753 zcmVaAma;6>Mz{wo!; zzVE*E0!2~0zmJQauM2~};wq_AiBa;e)Y~o`k6es?gcP(a+u0$KM@fpjF$Y zWiu0Wmwylq`LVZdM);#;<{dC~{y{;mQJ(&Op7DOJ&aT11jzONTAU(Aj8-HiC9xCRy z+fleDd2){I(bt=xdfYrC;ypZrL;QmxK`LuJP8Gaa@mBQlidXaJh8$dy8X)BWRB3K% zssK_M;2PxX85|6>B3Q3DgJGxc2Mf1|mq|+hBh^^fChxcvtrQ3~*Vok-YNRt`|Ct?G zQcJdFg)H!xed1qSnx`04QIM;ni!a!PZ_gx8eC)UFsr%ZCk00&s{lsy5W{rsf5GW*O zB~~!lx^JES=F>|X2Zgh*CvvgvoX_;m5^6?rer`d2acYrXR&jnF1J{gKH^lS>WjsW@rXhoi0UMkSPAAc~&lOz$8(puS$#*mHk7RsuNN>xcg(b=77DKHsPAw@d00)Bn zbO8pfg&A*4CvEMXuRBlP#PBi?#O~svWQLV|b9-h9=07m}ma+A<>#7}LTjix7vL&gx j1vxNy|zyw9^M$kGtdD@1Eo=D`w=^Uw-@fhqv#_o3huN(=GNIQZY%3JRV3QB^}8*6*bRE zBp4BFyQ2J-au9`|9z21rr1NC<5wtie_(afE+yFxQ3|?%J{v8uHa3>J}Wp~15*@DE9 zmarVG%Trn`tnuXKOC@M~57a(lG@Hi}%LU~N@JuuyySre2N^`-J3?3V69E&QNEr!FC zQ}6-kI|kzW_)OY29D>+$a5@?h|1%J$-E(3Lm2beu&ykiDNkaV)kO(F+ zisC?)HPXhFPIYMp6cPy4kM(LsIlvUiQAmLx)JZ)w6xS|R+KqzO+KYaUhGFT3c|V`Y zL>xjj;VfTDSeneylBanbUGM{0?f^XZ9t6M(gLR|><52}1rw>WOd2-*rvhOGVNjd~8 z3s!1{lgVURMjNz{60ayrW;9c-22`ih#w?;(D>SdMrbWa83y&4CR&RZd>*n$LjkK^E z8Cmw4d>E43g3SYDR7`QYWEIrDw$#yaSxwm-oEt-p96(*2o{i7WCZ9fDMoVZ-2{eFP zYQp*Y5>w!syn6>l*~vm#kX>jwc@`%8yPnG!&kX%u7T*E$Upv7ECoDtc9=PkbwmGN4bc- zcrY*qJ;U(fziA8T(EkG`xu(`j=>ZU9ifw?^NiRk2jx_kD8F18Hca^I1r_?Dodu0j3 zg(S*zno&WYgmR<;sWXDS05GGntU|+~TFS3XmtioX0EL45^T%sD&4m@WwK%sM4NZzC zqTt>6{H#27^LH~yK|)2XiR9tuB#zN~Kw7H4nhct{?3C@m1<8E=P4HjqFwob$Qo04Z z26&_LV(@hLxK&#A%(u8T3A$W|y*O@vPxsp7!5GDBW4V#NcAeZU#+{jj!oOe0^m%cI z8DQYr-b-uFVO!*m@u-3<5_LI;JzBhO4NfnnSDV_rMs*pP@(2d&kO(Zpm1G~o{-lN^ z)KImyYfoZOKf7W9$B}1rk!QG4XE~}3*Q0*402uPtJp_L2F7~in@Y6R;JIFM6CGq-d zQERP(&qy?~(^^_=?0lz6YW0_(!Ot`NgDjqp9(7R=kpHzhl5#nZ66j=~B?raQBHj96g%}Xm#eK@@1Ft=@7 zY%*##Mrf|NWHcM*mJW$@ZWvokW^BH9CqOmNDYYXLM02Urt^E~L@b4U0kG_}(*`+#tazE+K+X+&07cmRK6R2Y z98Ydfmcj9YjULrPoG+2y_!}<#Z^mu^$r+;7YGq4=>52|(tBH24RJ+L(^e~hk8v$^z zDid36k<5Kl&5iE5A~Xk0=0jtUV}hw4nB@aFUy*0r!{JSGOHc8@FAnE$I3ZSQsM_CM zS6I$WV~UNPfJ9zrS^Ml~h*YjS{l$j4i+S4#bT||G63vr6_jEcKp>V>A;voyi7 +,+aU/5k aЏR ESqqLqzgƮ؞b5[ǀcAbhm¶MYvIJ*)WwtO 4nKz>;Ak(d$T . EċKnа9Pwytħ;$1z?^2+3Q/V +z jm7Τmtю.$m` \ No newline at end of file diff --git a/gitflavio/objects/de/e63d0b4ae892fcc836df19d655ef78fba0df91 b/gitflavio/objects/de/e63d0b4ae892fcc836df19d655ef78fba0df91 new file mode 100644 index 0000000000000000000000000000000000000000..0905a83fca3a42b02f6c34eb9c2a5b52af27c161 GIT binary patch literal 753 zcmVaAma;6>Mz{wo!; zzVE*E0!2~0zmJQauM2~};wq_AiBa;e)Y~o`k6es?gcP(a+u0$KM@fpjF$Y zWiu0Wmwylq`LVZdM);#;<{dC~{y{;mQJ(&Op7DOJ&aT11jzONTAU(Aj8-HiC9xCRy z+fleDd2){I(bt=xdfYrC;ypZrL;QmxK`LuJP8Gaa@mBQlidXaJh8$dy8X)BWRB3K% zssK_M;2PxX85|6>B3Q3DgJGxc2Mf1|mq|+hBh^^fChxcvtrQ3~*Vok-YNRt`|Ct?G zQcJdFg)H!xed1qSnx`04QIM;ni!a!PZ_gx8eC)UFsr%ZCk00&s{lsy5W{rsf5GW*O zB~~!lx^JES=F>|X2Zgh*CvvgvoX_;m5^6?rer`d2acYrXR&jnF1J{gKH^lS>WjsW@rXhoi0UMkSPAAc~&lOz$8(puS$#*mHk7RsuNN>xcg(b=77DKHsPAw@d00)Bn zbO8pfg&A*4CvEMXuRBlP#PBi?#O~svWQGpuyuDYZC+6_2XY2U8ecqkYF3W6)Y)NWv jK@QNqVut!7oT@CPfwzx+7RX%h%;9mTO?wjn*jGlS@j-GL literal 0 HcmV?d00001 diff --git a/gitflavio/objects/e0/83fa81a6c387fc8ac548e8bc399b8335d781ef b/gitflavio/objects/e0/83fa81a6c387fc8ac548e8bc399b8335d781ef new file mode 100644 index 0000000000000000000000000000000000000000..0109fb6557e0656f814863d7f9e02982437b24f8 GIT binary patch literal 359 zcmV-t0hs=H0V^p=O;s>4uw*baFfcPQQE)6SPAv&ZOwudJC}7z6WZ6XyQ==V+o8Ri$ zy{djzv(ir1!~h5sobz*Y6Z29SPW}};>gbj;C(6-dUFoI;7r)fIaoYW$QYx-hcRU4k@g&R#f(dgjrBGo3w z3N^|nKRK}^Ge6HGHK!o82y7(dNkewsaJPFZ&x~yCCyS-VHo7}NRr@9u7iX5GIwpg( zfE5cb{OcT`-2F%8{EiR-vk&`TJUIn3I-n>&Ei)$-tn9|^N&9rCH(#k(zy1QZRon*= z*7#_snZc<=Wl)A FpA@|ws1yJI literal 0 HcmV?d00001 diff --git a/gitflavio/objects/e3/a5f4a180ecfd4e6b68f783989b3b9789b26fb6 b/gitflavio/objects/e3/a5f4a180ecfd4e6b68f783989b3b9789b26fb6 new file mode 100644 index 0000000000000000000000000000000000000000..42002e2b272544d297fdca4e31518432445c4a44 GIT binary patch literal 126 zcmV-^0D=E_0V^p=O;s>7F=j9{FfcPQQE)6SPAzfH&nqd)&&f$G(ksX)V6d|B6`ObT zRD#_Up1h~UtVa|$((gl+168JFrlYA}@bo-GL09^g6^9(A&aj`7Dv>PP22~%NT2z)= ggr;7mHK;<$BxzP+nQ8P=?#G310@GFk00ay;G$80Ug#Z8m literal 0 HcmV?d00001 diff --git a/gitflavio/objects/e4/e4b5179c616789e7dbdd52cb86a5becd0a9c14 b/gitflavio/objects/e4/e4b5179c616789e7dbdd52cb86a5becd0a9c14 new file mode 100644 index 0000000000000000000000000000000000000000..1335ddf0e2b707310b7f54280249f54657cdb3c0 GIT binary patch literal 1242 zcmV<01SR`;0o7OEZ`(Ey-gEv6wl7k$NXk|WE7mwek_Je+qzP&l>svufXPb*msw5RV ztNp+4PLyOuiV(jHmZy!A|e4*a(be zANT_wrdkvh_mjPFQp(f{C0V%7zV9*o1*HLItQM&a-tAROt&l|(r-cX&=m*eO*&)+l z%G{o*WpkWVhzSc{H8O0qVy`?g4Yt&>)=bh5T(jj&vjLN^U|(*oFRd*;!M75Ph5PR+ z4@eGWVpV_)gS*?|C^%$6Qsm`SNRyO$T*aC2zxXn7mcNp-aWN}U(&`dAnhe<5#wG9! zbQpYz>IKN6KZTu75s!@XIN2w=KW4SF28w>{Q=c6OPI8KgXP9E!u zH!IDP3X*Zl(a?wC?Z>#_+CZfh%UCFBU<6C+<)S%APjY|`$EHkEFy=YajeLuWSV#%_ za&&WzHiKhRaLLk~8#8!q(Nt9HVv|KQR(baN{xzGf3}oIT0r;U_Y{e0G}$< z>d<}rN40vKkm?h}b>o8nTrqZAjpm&aGsl?t6wuEq%H}VEJiy{N_9k59Sls9B zkUCN&%tx>brSDN$QWt)esxr@*RF+yxeknf~XiuGp7GfeIHJLbVB3iznCg)?wyLyJz1oQJ!sq4Vz z#WL=KU_zdsAVlXDAd{q^pi#sFvkL&%YWNKkDT6k-ig*ykSW2zcN^G|OPoT{Vt~}o% zMguYn1Cb2~8SWPU{ORgk&GBZ$tH0$&`}NFRcQ}-gf;iN$8nfqgZ#}uh>0)rx0!bAn zGE-?eN4@bB>_rardiHpQ9ysoURKii8h!-Mq2YpWAs90sIB54i#YOXZeZ_UMD$vMGu z@i%1qcmX-4*2q3DhdvU$@<3mOP%}i_-5kRZPAxLaF;1#5p!+j&TrCn$h*0HJt)M%E z`V_R)?_E-^8@Hr>gcsXcX)Q9DUh2^`TK5x=L{vWxAxA2q^l6@lLEOzfIYkAa%(ug; zRuv=TyTH5GquOq#8yQ9Ic3!9h)#Dk#ejz+To@Eygh@ER;kf0LXO_7-1BS+nI>aznM z;z9$Rd=C?jRNrUh4KM)drYi2VD)_YGx+K)*p^@sqqK;r!O!!*bS?QN)m;R{gzrDdhOrI3j zcn}%oA?wc%&y|0r%Ik+(Rqu|)uceXuG70 t|;dot Yo!ǾD5^6as%϶#Pٲ5\QPߎEP] \ No newline at end of file diff --git a/gitflavio/objects/e6/bb5deaba73fdea1c97b9c09bc810a9c92fa72d b/gitflavio/objects/e6/bb5deaba73fdea1c97b9c09bc810a9c92fa72d new file mode 100644 index 0000000000000000000000000000000000000000..1c971a000268adf9767470481721f4f0ea3c50f3 GIT binary patch literal 648 zcmV;30(bp*0lidRZ`wc*?6ZHx7NMMxAS6}N2NQz$a0n_buEFg?Qd!1##HSeBI@^h& zD*wHEcXmP$?MtN~8RXl!-PxI4)^WOq)ALWCKU@s5C^L*QXE2R3?!@ICPi{T24#N8~ zyLCjm;W6v|6|iLDfe9VIF1B&cF!%<{kCbhA!l+56b7$tei^YnxTJYNd{OH(js11~h zB-k?3_a`&g_uFvRYW0j~%$9_4NtvG}AyTZ9!7((t7?O_+=f~Jaqb^>twyZEn#-$J} zDZHEs({m=nmHom)^0h3=^DGXYTz(f7wh*Z>b>Sx7z}BD-33~+Hrwpn7n*P#uf7tE~ zaqaJ(ySLViKN-v{C>me_FBZa6)q}qL22^G=D4411AW<271{R~WTK6|-ow9SQwWh`e% zXOCDw(Kc1IiRc0NzDaD#3-H89O(^KJqz$C3h8A_V6mUrCzj1AqnULytnCGm>>$gMe zpr?QsatvO~$)oh~DivE3DawE{6WOAfaCG!);vFllM7o`f6tMQ0g;5IRg7OCl;~>xb zjW7@twngWxaY?i(hcO*3&ps+TuM=(8ofC|r2N6zsp0SVzaTo<6$2Bv_oG15;BADZT zVB5P2w(t_U=tk#z6uQWB?`V&CXPwfg7fxC*%aR)DdShK~6|Bul!D{lVCJ$8DJyMPx i*5f@i^OE?TRjVuhhi>>Us`qJ=rUmX(tM><5zQ1BBoJw{8 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/e7/b85297d4141c5e4190b68433d8e1cd5664c6a3 b/gitflavio/objects/e7/b85297d4141c5e4190b68433d8e1cd5664c6a3 new file mode 100644 index 0000000000000000000000000000000000000000..110a081173863e9f771ac3523bf28c9d7e34902c GIT binary patch literal 753 zcmVr-gAD%4Iw3w%DV1tDW#%7C!}ucplAF;Oz6qPw%>y`6~Ck1W6VP45Sj2s0X#lQC^&d=bp#n zHOf3D2w$?C-e$#;viVFbV)DDl=L5c8$1Krtg=~(PqIg7c{?GRnR6nPKj3QHI=ybDA zIolY5oHKo_pP1wsR+QEKweT^^3pKaDeX%UoZ(uk`88d|<)!n~F^Sw}pr@S3xd0JN~ z;SQxT9hPzdJ}YF21oz+jH=*3+Ga#G+1YT$!MU&Bw*?2M0}3%++>kTmZu-C_4yT`Q|-M zA$aeCEa0`^>)}=4jW6zWLa#d_avyoj0LSO3c*Fs>BG2!lou?CDt3+1JuPEe z2iyispeh8bCMt@q`{SG0WK`;F0-VO?(W!JmY>cODX}wOlh@b@@D$-6brI;o1-Z#6z z|8+oo3^beE)RMKfKXI|MZYHuI{6Bv+Lu@g7i-=ty+FfJO(PF8r z(B!)mB{p?a1j-^)8l+vqb$aIW(c8y;0LJuPzERUS-!8N6eOVBKQtHBLxXrqEDNA{8 zIvWf|)2VB?g3^0e#!D@@wxZQNwUKq#n^DJ!-f~oJYJk)ozp&6CYymU9zF$sfGqjsf zGhL%$4Zl`!^PC=xpn3HiWeC|8|y9{53Flw j^i0q$LDv)7Z}@9zK@gS}G=N?d zJF+CliM=GzAwlwJKHhsX%E~H3^KW0~ANF^MzZx!?MsZ2rs>VzUvn{7)C-NBG9VQd7 z$jXX7BU(u?H1;rM;sw!?XuXIioN*N`CkR$*NLKRj2{xD#ra0b+SME)mnR51aV^?Yl z@L%eh$VK!uTJHA<-kOl8y_S% zek5By;^55cVT}lb^!25{XZO~XGMBwSK|!LDmks|IVj6wfZbc3F8#6ow1{mE`%43Rz z4Cq>BZ6$UAczqVL9M60FISXddbj(a80P6T)M?QvTqQ)N4D~xM^@d`7S0k-;p^54DC zm1eWD3t$kNkUwbIU{VXLTd@03IBWNtT7?E+GQx6>vOLiZr67_{w!eRk2-V&^g~lhr zOU-YK$B4{r%lR((NkqxnO(7+N(tC8^?x~O)L z%{~?^{vH4p>|h%<>cBG+Wkx1?+y2!`Y>9e1)0hJtsB+`rN$_3U2E(LgWQc8=HO7D< z{Gftp{fL^wKw}US)n$_lnS{ZT7_LnMT8I!Mh9s`4M6kAGuXn+0F~gWGr!L#S;0CSM z4+COGvUi!Dx;y1#(8A}kxvFFPLpI87@ z?Tg?la~(&C#8h!&I@RAQsn737DYRh+urF!F*iklVkP2i2@1E;}bOtZl(!OPqM%=; zE)_?1$ort=Of>wpoMQ^hq;eUVdRpqhu6V+qn2y>N1|%U7F$pjL zXi0VazxUg{y$4<-ZOci1C}$Fjz}@cN?zek;dv_d6vVq#&x%d4Ke|hj|HlD4mjpE@X z%Hw`f=E<-O%bQtT?A=}eacyn;&Yd-NM{VBLhlri!kE7wc`Ap3w^UEYv7g?^JzdSm; zZAL`zaa6=3m8EK)518p@5$EsYd~*7P z9;d_jz8aV1thm3ueVLTw`Cx09O}Duj{=W)YP}^QxOQUIA%%Wkeo=;}U5fA=9XF>qF zo)@t?o~LCpjSsGe@vKa;6pvw=qJYvgqy1U(WRk>bx%OdA;WNvV_fZ+EZ177wELBer z58eEiWY_dk^#;&JIL{{?uOUEALUz2KL5`~|AJtwDvTUMy$z_`5aaa^};z zRD0^~POZCP14fe|s5Q|XCfVg>JPHArJ#`UHin^H02)ZiKFd3myHzz_si2oKd zObXK`+zs!ASssp)%P~Mw<V(nvTh&_5 zBeeCzn9_Ayn{qywBtvyEPltpH6^28@tY4+J&h0fihGN`i2U&gLSoqf39J#8ZCn)(BhzyUal)g?;?pO zqvAY(kcdUsrp3z-~h}tyBY( z#S&KAE|9Ssc4Ep=T*B9X*7yZ9-{dbCn0-;+NA3L{FrRV{m0T!+fdPL~sNJo53K3Xi zy8+ugF6Viw$OW}w5Xs0uChk~3b_;di^>(O)DE$gDof4sG;6S;y#82c9$EPRsk&)7$ zJvdldzy$I2Y?6)Qey|m6RKagpco-uwh8coxU-w^E57h3b^PWb(K!t!5BTADx-+Ald zWtt#9)?3^~3k!GFh$i%~QwBsA#621>eLmX1yv~HwUG?2}s;7BA+*9{DMpUO}$u+gz z#MJY+R2VL%4E!;L#(}M}qdW}V?2VaJRkq;Z6C8Vx4#C{XunZzRw)b#ZWA<`Je}qk;F)WFD)F zJe!hx{guSStT4}^5@b8ms3eaolId&`D{kA+i=Hl~$u+tl5NpNCm~|IXmXeu8nvUX& zXg(>`Q~8&vOH>O9ut623pvsQOizHdKxAo=WGF2~6UsJBtuZVTUNsqZ^tJ zcv$NMR(=!$IbnRA6lKv5SaNb<)2su;=KZvs@+Q=4!EFqXla380xGQX*XLBTSmIH?KuIu;-T zCL`p9NqK`XBkkZbC!BE{$}8%l1(RMffb+^jdKVP);Sf1lp!Oar3KSd7Cdg-t=rZOe zj{7$pz#FZJ=p$8N;z-Mjei-%xrvN3)F6NUh$&r*9&3*N40T=`()eew*SY|reA=3Wm zKGLHu@-5@quoSLH{83o6=bM#+GY6wn(0KpLw%jIR*xAt^yl%f zbNE?5;O8*>`CH)V=*`I~{$?%v-axVG8zJ%AZ|a+*K&I+|@H7BTURg zfOw0Qh712@Ovoqo$~&Bn?l~+C=E-DqB(GjX#n>d&ra8@>pz4F%l3eO4jetU37oxQq zVGO6hNTcJa`9?$O=s@byy`4vha+^Jtxd;*ZI-YF?xH96Rc{!#D8uLZ0qRS{jv7iBY zWelpO5whr5U7;Lxxr!$=4CZnLTQ{kOPBQ|@0h%vsKwmP_6Y{1UB`y>uVx)hl=U_3Q zeT-K8-GlJ>bUTv^#kx||y@IwA2z;eFsK!W})81q9DrI1*#_%hyPQ9MRwQ+M!7rj`&=yh~-0QHpWB@Viqm1<>1 zuE%N|C|heuO*)35Tenh{9#r5b*tVNKnoQJ?w>VHXiAvL0!~YUuin>Ri&WeUAnR)1)=d9^bqM2l0B<^CUp| z=Mig;$$ij7;UK6L#u3#PBqVu|#Yhc_x&?bYsLH&nd7TIJM$etX{yfmHT3(E^qI8dW zXT}Of-b;u>{2ijuJDkq?%oQsRe^|Uu+^Ne)oec(bYli}Q=c?LX*pm*VbKQC&Dh{6# zB1rmMg#jYXY6d?XEi|^F<18&|bJ3<9I~DELqK_qkN(7f+Yb#07Aa+Gb$@FQe*Q8tq zFNq{-LffFF<8f%7?<}0#HE~lbZwB|xP?>0LZeZt|yLcp{l zG}dPf^9~dqV8goZDPl4c2x~T5Fh9vysK8{cO}&w+%hFm=xk;QKA(QrPP-U$)E<P3PePV&=R9bpJ|keIY-xox!Gz}4uHf)w9Hp07cH7iK zll-FaVAgSFK}Gvs<|A;Ty)eO_X|4B5RF7wOY5p3;GX5@YZ5J8Z($eaXho_MztlNOR zAsc0?2f6Il4RgjEWVtlr5^UZQnr;!|3-)!%s*ZUjD>txRfO06f3G}dt-p7m3&Yhfd zI=qv;f(*OoS9wxm<3q?(m7zwDG)%AM10lp9>tcFGwBket%=*-ZDW@D3CwejQuGHPEDpAk^JlS9w3oYKQ}hdrvLP@3YtXd10gcB>V2ba>lG zO`R-nVwYHkbFHp2k4ZZ!h|=T)OIUOJgnT39(G~SGG|t`85z-1&APR|Gk*3yP&{W}AnT$N_?2kq+ z;qo_QSY56#w zA|Y{#=52%eex~@Cb+s>9V_8jgsXAwgK3Uu1a-WxKBJkZzvWU{uTD)J)9-fbrix~fh zFsSR-UBob}tcMX5>4U)J1}`XZlR}@9T&t zjOLiYtt8JN$YVa6zq)5u=QO4$B#aH%-RSt{=3sHNY@}6#lShBFPM$%8uc{0h z<4Vf#agt1N!s|}~ltYr_>mr2*yAo9XV6_ldzOFtPEKBr3$zdK35l{pmUslXB6%v$3 zDeLG>l4-(!;mb?kXBEXxv83H(1%BGP923}wMpytM8^O_ zb|7rR(b=k^Lg zG?Au{?3P)rcuwm2qqx%I*~kzgBYxhLtgUk3o(@R&}cbaX4OWJ*zVi&_s> z;Tkeuc&0}kIZSg=Ef;&uc`}N&d8rAtEtFg_pSb*KE4kgfIW)Xb+oS1tJb1G}_RA{% zW+Z7l&@D*^)&cQ%hu3L8L9Nc@CD?Dl?WvAaJN9MWne6N@`<134XEpjuy$i|eao3G- zc#O4>y%Py~&F6fkf(pxR6p5-hMfKn-y`YbC>*}|rQ;CN{06x#Va0AM#Mjc$o!#SOA zPL)?C#w9koNQEnMOV7@-nD&1?-9_n>czmKm9LUUEI{DX5XRcn+fk-a?AJIR!`-qvd>!;hUCpK1haFKhrodUV%i-Y~A#F##i`CPlJ{?7jo_Hh$-R>DDM zGc4Dz=*`mYUy3aCc4|#?y3`6_mz$@^I;uUcH?OX)Hjyh#H?7%6KPnvE$&rEVot-}0 z{Gmqit&`d>r!;`U{#{Q9dScykV<2ipWsPQP(AxCb;lazNC)DBdP-Xc7Ok7RDsJ%If z{S^a;lxFL5IL2HgQu~h3)4Q(RBSwNj9__zpeQ=vDFYobXMiV9Yix4q}qkoyfaFP{p z$0`SUyC70DAmd+iwtI0j&q)YJEJ8tQ)Oxp>rK?_S?r>kW3q=nMOxIF)1UMj(q!$^D zVS~N%@b$AdAwfNS^5*G*uet6KYn|36IGiN!_Z(OJR>sZ_--mV2@N*M?+o4}f`A|{H zEh&$Fj0DLf&)LY8aXUbLTfbNa3|VnjQDX%?LBQiNU<&9YZ6oGNI@NhDGbWqRRwM<9 z5WlX)0Uk8%%_FNDVveiE#shTQQA2&y46{CNGGUcegje_-s=oe_9#Ewp<9X1Wi9Z#y z)P4bOMfE9uHS3=zZ(ak*a?`K>;`G(a^)2;e91q`_n>#ecV@2&R0x0#bP3Kk(#G5`I z(CMo;$TF_@yNy*LE{%;vLa42Av08ICrEn@HkFu-q>#*$;F+T5M#GM%TR zE1#Ef8sqoXcqL>qMx(@z{JCv;g5Ry66OqTFem&U6jfLg)1jE{`?3kM--(Ei;6jl%T z%5Ox-C2g?h1oQq%xa g)I;YdYpY&F5VP3Q!e@_1B=HXGeq8(bKMhe_sPU7x4*&oF literal 0 HcmV?d00001 diff --git a/gitflavio/objects/f8/5893a04200dc8c30e75955bb12d4905110d2ff b/gitflavio/objects/f8/5893a04200dc8c30e75955bb12d4905110d2ff new file mode 100644 index 0000000000000000000000000000000000000000..2fc7b2de4bb881e5bcdec0ec5007d345209da3d7 GIT binary patch literal 802 zcmV+-1Ks?10o7JpZ`v>v-gAD%nN*33C}@+aNl>ATE1ptH?l(GmupPYR8aQZDjDtfL()ng9B49yM3^Ah;5h_5gFsuvIv9SJO&ZFW*LX}pcG_BS^<$Ynz zX=&G~B!dV;f)|A11Xh@1UGkt5w>&Qw7|zp-j0=vh=N9U9kI@BLc6QpwoN2mtJ4`XZ z!+f9J7Xr)OPM1gsa)pq)G&6Uo6 z_p&24z{xx?eFP=QJW~R}wwF#56C;2GA|o7ke#WuPIfaO^RQ0muEplpJ%}D$erL>Ln zZx7UC!*@7`8D}vTqN)b+PdSovr>cWvEZM2n-h;Aqe;wyJmKGtXC8K0fPMBj2agH8N z36%}d3y3bizRlqh%h0CM2#auUi^!{jRn4rCd%IouH_BtH@)zeu+&hj7O~0_cq1-Ls zo6Y9+&BArWtj#z8DnwHf6i~Bwq+VirIVqvUSj~-DVsXwnrV@z6YrV4d92o-Di`I*^ zito`nQ_HW%)>$qwZ@^d)Y`$PlGPQkB>Qo#^NSMk^nRJB!!PYDSi+!==Wy9Xc$fhV}Ih$Mw4ejZ&tCf zz0q(u=*?7)$&bNPf*aMZFsxR}KLp46mZnNz`jS+1sEel3fQq|kD{291q#I4yMUv(5 geaAma;6>Mz{wo!; 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 literal 0 HcmV?d00001 diff --git a/gitflavio/objects/pack/pack-16f64c98dc9785f95395d0429bffba92412a8d22.pack b/gitflavio/objects/pack/pack-16f64c98dc9785f95395d0429bffba92412a8d22.pack new file mode 100644 index 0000000000000000000000000000000000000000..f06fcde7ad77566fd96adb68d14115f62bbf0f97 GIT binary patch literal 46673 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 HCi{N?j3RKe literal 0 HcmV?d00001 diff --git a/gitflavio/objects/pack/pack-16f64c98dc9785f95395d0429bffba92412a8d22.rev b/gitflavio/objects/pack/pack-16f64c98dc9785f95395d0429bffba92412a8d22.rev new file mode 100644 index 0000000000000000000000000000000000000000..7f9f82a39958648e574936b8a98fe99d994a6443 GIT binary patch literal 368 zcmXZUxh?|$0LJmU@0&V`Qg + */ +function plugin_urbackup_get_classes(): array +{ + return [ + Config::class, + Profile::class, + Server::class, + ServerAsset::class, + PluginUrbackupMassiveAction::class, + ]; +} + +/** + * Declare plugin massive actions. + * + * In GLPI this hook receives the current itemtype as string, + * for example Computer, Printer, Peripheral. + * + * It does not receive a MassiveAction object. + * + * @param mixed $type Current itemtype + * + * @return array + */ +function plugin_urbackup_MassiveActions($type): array +{ + $actions = []; + + if (!is_string($type) || $type === '') { + return $actions; + } + + if (!Config::isItemtypeEnabled($type)) { + return $actions; + } + + if (Profile::canCurrentUser(UPDATE) || Profile::canCurrentUser(CREATE)) { + $actions[ + PluginUrbackupMassiveAction::class + . \MassiveAction::CLASS_ACTION_SEPARATOR + . PluginUrbackupMassiveAction::ACTION_CONNECT_SERVER + ] = __('UrBackup - connect to server', 'urbackup'); + } + + if (Profile::canCurrentUser(UPDATE)) { + $actions[ + PluginUrbackupMassiveAction::class + . \MassiveAction::CLASS_ACTION_SEPARATOR + . PluginUrbackupMassiveAction::ACTION_DISCONNECT_SERVER + ] = __('UrBackup - disconnect from server', 'urbackup'); + } + + return $actions; +} \ No newline at end of file diff --git a/install/install.php b/install/install.php new file mode 100644 index 0000000..259e2ef --- /dev/null +++ b/install/install.php @@ -0,0 +1,348 @@ +runFile() + * - schema evolution using Migration addField(), addKey(), executeMigration() + * + * ------------------------------------------------------------------------- + */ + +use GlpiPlugin\Urbackup\Config; +use GlpiPlugin\Urbackup\Profile; + +if (!defined('GLPI_ROOT')) { + die('Sorry. You cannot access this file directly.'); +} + +/** + * Install or update plugin database schema and default data. + * + * @return bool + */ +function plugin_urbackup_install_process(): bool +{ + $migration = new Migration(PLUGIN_URBACKUP_VERSION); + + $migration->displayMessage(__('UrBackup plugin installation', 'urbackup')); + + plugin_urbackup_install_create_initial_schema($migration); + + plugin_urbackup_install_update_configs_table($migration); + plugin_urbackup_install_update_assettypes_table($migration); + plugin_urbackup_install_update_servers_table($migration); + plugin_urbackup_install_update_serverassets_table($migration); + plugin_urbackup_install_update_profiles_table($migration); + + $migration->executeMigration(); + + Config::ensureDefaultConfiguration(); + Profile::installRights(); + + return true; +} + +/** + * Create initial database schema from SQL file. + * + * @param Migration $migration Migration instance + * + * @return void + */ +function plugin_urbackup_install_create_initial_schema(Migration $migration): void +{ + global $DB; + + $required_tables = [ + 'glpi_plugin_urbackup_configs', + 'glpi_plugin_urbackup_assettypes', + 'glpi_plugin_urbackup_servers', + 'glpi_plugin_urbackup_serverassets', + 'glpi_plugin_urbackup_profiles', + ]; + + $missing_table_found = false; + + foreach ($required_tables as $table) { + if (!$DB->tableExists($table)) { + $missing_table_found = true; + break; + } + } + + if (!$missing_table_found) { + return; + } + + $migration->displayMessage(__('Creating UrBackup plugin database schema', 'urbackup')); + + $db_file = PLUGIN_URBACKUP_DIR . '/install/mysql/plugin_urbackup-empty.sql'; + + if (!file_exists($db_file)) { + $migration->displayMessage( + sprintf( + __('Database schema file not found: %s', 'urbackup'), + $db_file + ) + ); + + return; + } + + if (!$DB->runFile($db_file)) { + $migration->displayMessage(__('Error while creating UrBackup plugin database schema', 'urbackup')); + } +} + +/** + * Update configs table. + * + * @param Migration $migration Migration instance + * + * @return void + */ +function plugin_urbackup_install_update_configs_table(Migration $migration): void +{ + global $DB; + + $table = 'glpi_plugin_urbackup_configs'; + + if (!$DB->tableExists($table)) { + return; + } + + $migration->addField($table, 'name', 'string', [ + 'value' => '', + 'after' => 'id', + ]); + + $migration->addField($table, 'value', 'text', [ + 'after' => 'name', + ]); + + $migration->addField($table, 'date_creation', 'timestamp'); + $migration->addField($table, 'date_mod', 'timestamp'); + + $migration->addKey($table, 'name'); +} + +/** + * Update assettypes table. + * + * @param Migration $migration Migration instance + * + * @return void + */ +function plugin_urbackup_install_update_assettypes_table(Migration $migration): void +{ + global $DB; + + $table = 'glpi_plugin_urbackup_assettypes'; + + if (!$DB->tableExists($table)) { + return; + } + + $migration->addField($table, 'itemtype', 'string', [ + 'value' => '', + 'after' => 'id', + ]); + + $migration->addField($table, 'is_active', 'bool', [ + 'value' => 0, + 'after' => 'itemtype', + ]); + + $migration->addField($table, 'is_default', 'bool', [ + 'value' => 0, + 'after' => 'is_active', + ]); + + $migration->addField($table, 'date_creation', 'timestamp'); + $migration->addField($table, 'date_mod', 'timestamp'); + + $migration->addKey($table, 'itemtype'); + $migration->addKey($table, 'is_active'); +} + +/** + * Update servers table. + * + * @param Migration $migration Migration instance + * + * @return void + */ +function plugin_urbackup_install_update_servers_table(Migration $migration): void +{ + global $DB; + + $table = 'glpi_plugin_urbackup_servers'; + + if (!$DB->tableExists($table)) { + return; + } + + $migration->addField($table, 'entities_id', 'integer', [ + 'value' => 0, + 'after' => 'id', + ]); + + $migration->addField($table, 'is_recursive', 'bool', [ + 'value' => 0, + 'after' => 'entities_id', + ]); + + $migration->addField($table, 'name', 'string', [ + 'value' => '', + 'after' => 'is_recursive', + ]); + + $migration->addField($table, 'locations_id', 'integer', [ + 'value' => 0, + 'after' => 'name', + ]); + + $migration->addField($table, 'ip_address', 'string', [ + 'value' => '', + 'after' => 'locations_id', + ]); + + $migration->addField($table, 'port', 'integer', [ + 'value' => 55414, + 'after' => 'ip_address', + ]); + + $migration->addField($table, 'protocol', 'string', [ + 'value' => 'http', + 'after' => 'port', + ]); + + $migration->addField($table, 'server_version', 'string', [ + 'after' => 'protocol', + ]); + + $migration->addField($table, 'api_username', 'string', [ + 'after' => 'server_version', + ]); + + $migration->addField($table, 'api_password', 'text', [ + 'after' => 'api_username', + ]); + + $migration->addField($table, 'ignore_ssl', 'bool', [ + 'value' => 0, + 'after' => 'api_password', + ]); + + $migration->addField($table, 'is_active', 'bool', [ + 'value' => 1, + 'after' => 'ignore_ssl', + ]); + + $migration->addField($table, 'last_api_status', 'bool', [ + 'value' => 0, + 'after' => 'is_active', + ]); + + $migration->addField($table, 'last_api_message', 'text', [ + 'after' => 'last_api_status', + ]); + + $migration->addField($table, 'last_api_check', 'timestamp', [ + 'after' => 'last_api_message', + ]); + + $migration->addField($table, 'comment', 'text', [ + 'after' => 'last_api_check', + ]); + + $migration->addField($table, 'date_creation', 'timestamp'); + $migration->addField($table, 'date_mod', 'timestamp'); + + $migration->addKey($table, 'name'); + $migration->addKey($table, 'entities_id'); + $migration->addKey($table, 'locations_id'); + $migration->addKey($table, 'is_active'); +} + +/** + * Update serverassets table. + * + * @param Migration $migration Migration instance + * + * @return void + */ +function plugin_urbackup_install_update_serverassets_table(Migration $migration): void +{ + global $DB; + + $table = 'glpi_plugin_urbackup_serverassets'; + + if (!$DB->tableExists($table)) { + return; + } + + $migration->addField($table, 'plugin_urbackup_servers_id', 'integer', [ + 'value' => 0, + 'after' => 'id', + ]); + + $migration->addField($table, 'itemtype', 'string', [ + 'value' => '', + 'after' => 'plugin_urbackup_servers_id', + ]); + + $migration->addField($table, 'items_id', 'integer', [ + 'value' => 0, + 'after' => 'itemtype', + ]); + + $migration->addKey($table, 'plugin_urbackup_servers_id'); + $migration->addKey($table, ['itemtype', 'items_id'], 'item'); +} + +/** + * Update profiles table. + * + * @param Migration $migration Migration instance + * + * @return void + */ +function plugin_urbackup_install_update_profiles_table(Migration $migration): void +{ + global $DB; + + $table = 'glpi_plugin_urbackup_profiles'; + + if (!$DB->tableExists($table)) { + return; + } + + $migration->addField($table, 'profiles_id', 'integer', [ + 'value' => 0, + 'after' => 'id', + ]); + + $migration->addField($table, 'rightname', 'string', [ + 'value' => '', + 'after' => 'profiles_id', + ]); + + $migration->addField($table, 'rights', 'integer', [ + 'value' => 0, + 'after' => 'rightname', + ]); + + $migration->addField($table, 'date_creation', 'timestamp'); + $migration->addField($table, 'date_mod', 'timestamp'); + + $migration->addKey($table, ['profiles_id', 'rightname'], 'profile_right'); +} diff --git a/install/mysql/plugin_urbackup-empty.sql b/install/mysql/plugin_urbackup-empty.sql new file mode 100644 index 0000000..3562526 --- /dev/null +++ b/install/mysql/plugin_urbackup-empty.sql @@ -0,0 +1,69 @@ +CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_configs` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(255) NOT NULL DEFAULT '', + `value` TEXT DEFAULT NULL, + `date_creation` TIMESTAMP NULL DEFAULT NULL, + `date_mod` TIMESTAMP NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_assettypes` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `itemtype` VARCHAR(255) NOT NULL DEFAULT '', + `is_active` TINYINT NOT NULL DEFAULT 0, + `is_default` TINYINT NOT NULL DEFAULT 0, + `date_creation` TIMESTAMP NULL DEFAULT NULL, + `date_mod` TIMESTAMP NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `itemtype` (`itemtype`), + KEY `is_active` (`is_active`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_servers` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `entities_id` INT UNSIGNED NOT NULL DEFAULT 0, + `is_recursive` TINYINT NOT NULL DEFAULT 0, + `name` VARCHAR(255) NOT NULL DEFAULT '', + `locations_id` INT UNSIGNED NOT NULL DEFAULT 0, + `ip_address` VARCHAR(255) NOT NULL DEFAULT '', + `port` INT UNSIGNED NOT NULL DEFAULT 55414, + `protocol` VARCHAR(10) NOT NULL DEFAULT 'http', + `server_version` VARCHAR(64) DEFAULT NULL, + `api_username` VARCHAR(255) DEFAULT NULL, + `api_password` TEXT DEFAULT NULL, + `ignore_ssl` TINYINT NOT NULL DEFAULT 0, + `is_active` TINYINT NOT NULL DEFAULT 1, + `last_api_status` TINYINT NOT NULL DEFAULT 0, + `last_api_message` TEXT DEFAULT NULL, + `last_api_check` TIMESTAMP NULL DEFAULT NULL, + `comment` TEXT DEFAULT NULL, + `date_creation` TIMESTAMP NULL DEFAULT NULL, + `date_mod` TIMESTAMP NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `name` (`name`), + KEY `entities_id` (`entities_id`), + KEY `locations_id` (`locations_id`), + KEY `is_active` (`is_active`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_serverassets` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `plugin_urbackup_servers_id` INT UNSIGNED NOT NULL DEFAULT 0, + `itemtype` VARCHAR(255) NOT NULL DEFAULT '', + `items_id` INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `plugin_urbackup_servers_id` (`plugin_urbackup_servers_id`), + KEY `item` (`itemtype`, `items_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; + +CREATE TABLE IF NOT EXISTS `glpi_plugin_urbackup_profiles` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `profiles_id` INT UNSIGNED NOT NULL DEFAULT 0, + `rightname` VARCHAR(255) NOT NULL DEFAULT '', + `rights` INT NOT NULL DEFAULT 0, + `date_creation` TIMESTAMP NULL DEFAULT NULL, + `date_mod` TIMESTAMP NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `profile_right` (`profiles_id`, `rightname`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; \ No newline at end of file diff --git a/install/uninstall.php b/install/uninstall.php new file mode 100644 index 0000000..454285e --- /dev/null +++ b/install/uninstall.php @@ -0,0 +1,59 @@ +displayMessage(__('UrBackup plugin uninstallation', 'urbackup')); + + Profile::uninstallRights(); + + plugin_urbackup_migration_drop_table($migration, 'glpi_plugin_urbackup_profiles'); + plugin_urbackup_migration_drop_table($migration, 'glpi_plugin_urbackup_serverassets'); + plugin_urbackup_migration_drop_table($migration, 'glpi_plugin_urbackup_servers'); + plugin_urbackup_migration_drop_table($migration, 'glpi_plugin_urbackup_assettypes'); + plugin_urbackup_migration_drop_table($migration, 'glpi_plugin_urbackup_configs'); + + $migration->executeMigration(); + + return true; +} + +/** + * Drop a plugin table through Migration. + * + * @param Migration $migration Migration instance + * @param string $table Table name + * + * @return void + */ +function plugin_urbackup_migration_drop_table(Migration $migration, $table): void +{ + global $DB; + + if (!$DB->tableExists($table)) { + return; + } + + $migration->dropTable($table); +} \ No newline at end of file diff --git a/js/urbackup.js b/js/urbackup.js new file mode 100644 index 0000000..e4e4b51 --- /dev/null +++ b/js/urbackup.js @@ -0,0 +1,87 @@ +/** + * UrBackup API Test JavaScript + */ +(function () { + 'use strict'; + + function testApi(serverId, resultBox) { + if (!serverId || !resultBox) return; + + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/plugins/urbackup/ajax/server_test.php', true); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.timeout = 8000; + + xhr.onload = function () { + if (xhr.status === 200) { + try { + var data = JSON.parse(xhr.responseText); + if (data.success) { + resultBox.innerHTML = ' ' + 'API connection OK' + ''; + } else { + var message = data.message || 'Connection failed'; + var isNetwork = /timeout|could not resolve|couldn't connect|connection refused|connection timed out|network is unreachable|no route to host|returned HTTP status/i.test(message); + var statusClass = isNetwork ? 'text-warning' : 'text-danger'; + var icon = isNetwork ? 'ti-wifi-off' : 'ti-x'; + var label = isNetwork ? 'Server unreachable' : 'API connection failed'; + resultBox.innerHTML = ' ' + label + '
' + message + ''; + } + } catch (e) { + resultBox.innerHTML = ' Error'; + } + } else { + resultBox.innerHTML = ' HTTP ' + xhr.status + ''; + } + }; + + xhr.ontimeout = function () { + resultBox.innerHTML = ' Server unreachable
Connection timeout'; + }; + + xhr.onerror = function () { + resultBox.innerHTML = ' Server unreachable
Network error'; + }; + + xhr.send('id=' + encodeURIComponent(serverId)); + } + + function initApiStatusCheck() { + var statusBox = document.getElementById('plugin-urbackup-api-status'); + if (!statusBox) return; + if (statusBox._initialized) return; + statusBox._initialized = true; + + var serverId = statusBox.getAttribute('data-server-id'); + if (serverId) { + testApi(serverId, statusBox); + } + } + + function initApiTestButtons() { + var buttons = document.querySelectorAll('.plugin-urbackup-test-api'); + + buttons.forEach(function (button) { + if (button._initialized) return; + button._initialized = true; + + button.addEventListener('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + + var serverId = button.getAttribute('data-server-id'); + var resultBox = document.getElementById('plugin-urbackup-api-test-result'); + + testApi(serverId, resultBox); + }); + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initApiStatusCheck); + document.addEventListener('DOMContentLoaded', initApiTestButtons); + } else { + setTimeout(initApiStatusCheck, 100); + setTimeout(initApiTestButtons, 100); + } +})(); diff --git a/locales/de_DE.mo b/locales/de_DE.mo new file mode 100644 index 0000000000000000000000000000000000000000..9c730d7a658305cc598ea196667e6dadc80389b3 GIT binary patch literal 3126 zcmZvd&5ImG6u?W3Mpu)Vn8dHB6+faT=}FKak{H5fX9L5o?vmY25E1E_uAXV9r+QM| zy_-bQYdndFcu+6m#hW6ihk(Ha(Z4`RL=ZfQC<=P;qQBSOGdnv;Gu^-GS5>dxSH1pe z+lF@(*M9C@+^=4z)DigO^*p$~y+Nt1@H@BzUa9ye+{pXJ4dwV2_z>^A;TAXzZ-qe@r`QyT{wyS6L=5&3(CHmZc>V$+QCEY*#%{veQ+Cm1~OI6!585Yd>(!VZ-$#t zvJ>uv;=fn%C=`F5g`%%r$HSdHNTM{hQfz8t#W0Iw*SIhJ;GJ2gRNbq3HP(O8&mC#xFyO@6T`r9;1jbU3~#1 zzL%iP{~k)5|ET2uRqs1*E9HA2rCeNMmz1;A(VjxG_IQB5;-_3<-)?TvD|-$u&7Zy8 zQU{N5OPvia+*Xfpvt8k*_#~IaWFNQWUasxj92xa6_x;?G1M%lkZc4j2f1vP9D6y4G zYECYRndC`wLuiUzOa5@Xs0%eY-_WhZTGPs+#Ok@3aY^V$u?fbdY365nswRt7FilOS zualh5g`)uFq`8G(^RL8Wsk!{y%H8mAS zt*&11myOO6otZQnF;hm3&2(d4`$6DL%Gy#AC+*>X>-Wq+E*9XQnkrpYQ_lFA(XDb- zWc89Otxr9TGL&EF3HaQO|D= zB@NqhriTmu`kABhu?1nG59ryXtr|8n&Jylk5*Rfb*{%r&Ry>!C#6$uXM(x~@J_ZIJ zR&%vK%#dUz>zMV~(MgCfO0_S!tzAn)Ta0^R93L57S{}f8m%PkeuMv2s$`U`Ub6195AFLg! z9h{i;Z9Av-4(q_YRG*&U2xR2PTRhiCmYK8Jfqv{q_J}4Kj$9vIT0G%BJt0S4lr|$9 zLyxFQpc#XP3dANG9-SmlRRTR8Cj-Js{ zHN(kOr|^y&+0v-h_nXG`-Ri{M>I^Lf!v zFsW=BUSr;yEV{^`U3i2wOS(zSUgeyTUNQ)@m@lyz>XDKil!7K1nPI(^+O~o3f@R~* zi&Zuv5W~JS(n`E42J!0W#FUkb&d`{xI)x!=w)&9`BEqffoRz3*En`CsX)@mNvh6f% zw`?+E$NFVsLu1Q|Rx=F2u;I{eX!&^EwW}BVAzi4-nwl{je>%?~jJjKOp2)bY7##*? z;N-OvEvZVewWyU;6SAs6NHKd{rG*KVF;{h=S(BZoXP2=kyU?(nr1+{}TQZW7Ehnj= z5{p)Zn{~gkLN`q)S-3Ve;i1Nwt`Eb(Idkjlmp?_9J}Gh<)Ix@k wa}oGChv6EX%A2V&wZ2nv()jvyGQJ*cVTXOFGGzSS&(jsZ6OW5W-pg+FKNPD;WB>pF literal 0 HcmV?d00001 diff --git a/locales/de_DE.po b/locales/de_DE.po new file mode 100644 index 0000000..112ac1a --- /dev/null +++ b/locales/de_DE.po @@ -0,0 +1,158 @@ +msgid "" +msgstr "" +"Project-Id-Version: urbackup 0.4.0\n" +"Language: de_DE\n" +"Content-Type: text/plain; charset=UTF-8\n" + +msgid "UrBackup" +msgstr "UrBackup" + +msgid "UrBackup server" +msgstr "UrBackup-Server" + +msgid "UrBackup servers" +msgstr "UrBackup-Server" + +msgid "UrBackup configuration" +msgstr "UrBackup-Konfiguration" + +msgid "UrBackup rights" +msgstr "UrBackup-Berechtigungen" + +msgid "No UrBackup server linked." +msgstr "Kein UrBackup-Server verknüpft." + +msgid "UrBackup server selection" +msgstr "UrBackup-Serverauswahl" + +msgid "Root location ID" +msgstr "Root-Location-ID" + +msgid "Asset location ID" +msgstr "Asset-Location-ID" + +msgid "The asset is in a sub-location. The plugin will use the server assigned to the root location." +msgstr "Das Asset befindet sich in einer Unter-Location. Der Server der Root-Location wird verwendet." + +msgid "No UrBackup server available for the root location of this asset." +msgstr "Kein UrBackup-Server für die Root-Location dieses Assets verfügbar." + +msgid "Available servers for root location" +msgstr "Verfügbare Server für die Root-Location" + +msgid "Connect" +msgstr "Verbinden" + +msgid "Disconnect" +msgstr "Trennen" + +msgid "UrBackup status" +msgstr "UrBackup-Status" + +msgid "Linked server" +msgstr "Verknüpfter Server" + +msgid "Client name" +msgstr "Clientname" + +msgid "Client IP address" +msgstr "Client-IP-Adresse" + +msgid "Client version" +msgstr "Client-Version" + +msgid "Online" +msgstr "Online" + +msgid "Offline" +msgstr "Offline" + +msgid "State" +msgstr "Status" + +msgid "Actions" +msgstr "Aktionen" + +msgid "Info / Log" +msgstr "Info / Protokoll" + +msgid "Create client in UrBackup" +msgstr "Client in UrBackup erstellen" + +msgid "Backup commands" +msgstr "Backup-Befehle" + +msgid "Incremental file backup" +msgstr "Inkrementelles Dateibackup" + +msgid "Full file backup" +msgstr "Vollständiges Dateibackup" + +msgid "Incremental image backup" +msgstr "Inkrementelles Image-Backup" + +msgid "Full image backup" +msgstr "Vollständiges Image-Backup" + +msgid "Internet mode" +msgstr "Internetmodus" + +msgid "Default directories" +msgstr "Standardverzeichnisse" + +msgid "Recent backups" +msgstr "Letzte Backups" + +msgid "Client logs" +msgstr "Client-Protokolle" + +msgid "Delete client from UrBackup server" +msgstr "Client vom UrBackup-Server löschen" + +msgid "The client deletion will be queued on the UrBackup server and may require up to 24 hours." +msgstr "Die Löschung des Clients wird auf dem UrBackup-Server in die Warteschlange gestellt und kann bis zu 24 Stunden dauern." + +msgid "API connection status" +msgstr "API-Verbindungsstatus" + +msgid "API connection OK" +msgstr "API-Verbindung OK" + +msgid "API connection failed" +msgstr "API-Verbindung fehlgeschlagen" + +msgid "Server unreachable" +msgstr "Server nicht erreichbar" + +msgid "No IP address configured" +msgstr "Keine IP-Adresse konfiguriert" + +msgid "Checking..." +msgstr "Überprüfung..." + +msgid "Click Save to test connection" +msgstr "Klicken Sie auf Speichern, um die Verbindung zu testen" + +msgid "Linked clients" +msgstr "Verknüpfte Clients" + +msgid "Unlinked clients" +msgstr "Nicht verknüpfte Clients" + +msgid "No linked assets" +msgstr "Keine verknüpften Assets" + +msgid "No unlinked clients found on UrBackup server" +msgstr "Keine nicht verknüpften Clients auf dem UrBackup-Server gefunden" + +msgid "API connection not working. Save server to test connection." +msgstr "API-Verbindung funktioniert nicht. Speichern Sie den Server, um die Verbindung zu testen." + +msgid "Status" +msgstr "Status" + +msgid "Last backup" +msgstr "Letztes Backup" + +msgid "Show" +msgstr "Anzeigen" \ No newline at end of file diff --git a/locales/en_GB.mo b/locales/en_GB.mo new file mode 100644 index 0000000000000000000000000000000000000000..31a95026c46ca2c322f0f6770300750573cc58d9 GIT binary patch literal 2979 zcmeH{&u`pB6vqcBl!k`VLd&lJ)AB2{T{oagMg1X^CZt^vNYo^hA3`v($6hyHdpov6 zD=Hyz0&zkJkdU}>LE?bm(qj)CKuBD;QN)Qe5=e07^K9>Cv+xIS$Y^Ij_M4eE^SmyW#h6FTCFH54fH8?c3_{UGQPv_rqQAS$G>< zfe%0f#n0P~{ey;|!P~Ka0ma`{crUyL?}R_Y1Mqh^26x}A)E#gFioX`T8$JW?f#;#j z>%npOR>Sw2@lW71_OIZ5@J}fFj^CmbKed-XInM-?eGb7r@FZlaT7a*?75Fmz2HpzC zak3BYgA#wM;VCHbJP*ZR)Yvb#Pcl_e?LJv&vhvJ{sl$f9Xo2>4nsNTF)04dLecjXDEc}m`o0fE z-;W!9-i&_@Mc*Hw=sV72*>@5mm70Qbo?}q2lX3p2z8wc6I+irL|DBl_r`<6U`+5 zqA8M8xsiGyO!Wj^O)s{!pQWkwi#SX5!gHgR(8P%iMy+{aiZWNz)hd|hw$MrDo61Xj zR!y(7&a5U@=hm%Tm+LTd+GW|sGr&+IW`R}nak^%M{wZF_hBOhu!Z<1&>0_WDVKY~oL53tV zMaOQ=j?O}iajuQz*849HNwv497Q&E6s}|A<=d3Tuse{kex#65^N@J-i2wkS+!0$+n zs3pqDs%6el?WWn5oEV*#tF3-jd0NJMNf|NpW{k#VPH|wXD@>NgQHmH+Fqk(Ln^$>n z@*PM%rQ9nwt5#nuZOK9`vhZI@rGf66%i7tCB~_uRS+;oUsP1H?%RSYfKX@1@UtNa{ zvx}onk#D@$2OoVWo}|8xqa(vh>kGK(vJ1>@wS(4+b%`I>rK_daQ{GW;YHZ%5QAzC` z*EW6q%;_<%KtX<5%a?oD3VW$I(o0O7p3o%2k?T_{%X6)#$40OG)~8|X)6jh1w>}Mn KZ_fYs)9?=^Hv%pI literal 0 HcmV?d00001 diff --git a/locales/en_GB.po b/locales/en_GB.po new file mode 100644 index 0000000..ba3596d --- /dev/null +++ b/locales/en_GB.po @@ -0,0 +1,158 @@ +msgid "" +msgstr "" +"Project-Id-Version: urbackup 0.4.0\n" +"Language: en_GB\n" +"Content-Type: text/plain; charset=UTF-8\n" + +msgid "UrBackup" +msgstr "UrBackup" + +msgid "UrBackup server" +msgstr "UrBackup server" + +msgid "UrBackup servers" +msgstr "UrBackup servers" + +msgid "UrBackup configuration" +msgstr "UrBackup configuration" + +msgid "UrBackup rights" +msgstr "UrBackup rights" + +msgid "No UrBackup server linked." +msgstr "No UrBackup server linked." + +msgid "UrBackup server selection" +msgstr "UrBackup server selection" + +msgid "Root location ID" +msgstr "Root location ID" + +msgid "Asset location ID" +msgstr "Asset location ID" + +msgid "The asset is in a sub-location. The plugin will use the server assigned to the root location." +msgstr "The asset is in a sub-location. The plugin will use the server assigned to the root location." + +msgid "No UrBackup server available for the root location of this asset." +msgstr "No UrBackup server available for the root location of this asset." + +msgid "Available servers for root location" +msgstr "Available servers for root location" + +msgid "Connect" +msgstr "Connect" + +msgid "Disconnect" +msgstr "Disconnect" + +msgid "UrBackup status" +msgstr "UrBackup status" + +msgid "Linked server" +msgstr "Linked server" + +msgid "Client name" +msgstr "Client name" + +msgid "Client IP address" +msgstr "Client IP address" + +msgid "Client version" +msgstr "Client version" + +msgid "Online" +msgstr "Online" + +msgid "Offline" +msgstr "Offline" + +msgid "State" +msgstr "State" + +msgid "Actions" +msgstr "Actions" + +msgid "Info / Log" +msgstr "Info / Log" + +msgid "Create client in UrBackup" +msgstr "Create client in UrBackup" + +msgid "Backup commands" +msgstr "Backup commands" + +msgid "Incremental file backup" +msgstr "Incremental file backup" + +msgid "Full file backup" +msgstr "Full file backup" + +msgid "Incremental image backup" +msgstr "Incremental image backup" + +msgid "Full image backup" +msgstr "Full image backup" + +msgid "Internet mode" +msgstr "Internet mode" + +msgid "Default directories" +msgstr "Default directories" + +msgid "Recent backups" +msgstr "Recent backups" + +msgid "Client logs" +msgstr "Client logs" + +msgid "Delete client from UrBackup server" +msgstr "Delete client from UrBackup server" + +msgid "The client deletion will be queued on the UrBackup server and may require up to 24 hours." +msgstr "The client deletion will be queued on the UrBackup server and may require up to 24 hours." + +msgid "API connection status" +msgstr "API connection status" + +msgid "API connection OK" +msgstr "API connection OK" + +msgid "API connection failed" +msgstr "API connection failed" + +msgid "Server unreachable" +msgstr "Server unreachable" + +msgid "No IP address configured" +msgstr "No IP address configured" + +msgid "Checking..." +msgstr "Checking..." + +msgid "Click Save to test connection" +msgstr "Click Save to test connection" + +msgid "Linked clients" +msgstr "Linked clients" + +msgid "Unlinked clients" +msgstr "Unlinked clients" + +msgid "No linked assets" +msgstr "No linked assets" + +msgid "No unlinked clients found on UrBackup server" +msgstr "No unlinked clients found on UrBackup server" + +msgid "API connection not working. Save server to test connection." +msgstr "API connection not working. Save server to test connection." + +msgid "Status" +msgstr "Status" + +msgid "Last backup" +msgstr "Last backup" + +msgid "Show" +msgstr "Show" \ No newline at end of file diff --git a/locales/it_IT.mo b/locales/it_IT.mo new file mode 100644 index 0000000000000000000000000000000000000000..505476e9934cac4c29eb6a086daf07671e01517f GIT binary patch literal 3083 zcmZ{lONtj5n2d(wmxSV!UYL@9W3R?0}u=`At_>ef2&5_03Nk z*1V;-c5!d!e)%e;_QOk8^TqYmHA=0A7vVt{2h+L_17wO3)~FFUIX3^pMZD3<51@H z;5a-}@%8Hc+i-&MkKtYLPbmA2U#AowwUICJXET(2cESztG00Rk3!jGz@M(A+UJu7H zvI%a25`UxO0VwetfnqPL#!td$c|Hrp{?Ab2_!~-|*I^XXY8#X|cfkgH9EyJtybYd# z55aR#^7R>%_`iXY|L>vr^9#hZ`U}cF|3cYk;|*ngcS4yz1I6w$Q1a<3o`s^151`op z9E#r;p~Q6w-Uok!GjNWbh?E-(5Y5f8w*)l|AcA^W$M| ziFXILDqc+JRm+bS%#`66KgW7gL0LN z^+Hm5qo!=fzc4X z(u#w`b_v0(o;f@|vY_MjZawWo)rvEl#H73H+g44xc-gjVFJ}BuP9$L+7v>4&<3$}| zHCLN{g`_gsl3iUL?K=#*R2!*n^GX(?V(+G!?R5BQ)ofhgm<^;jW$>vwHe7R!H0BCH z=sczd!IEf1%^@eN=JBD}O|vaEF*?l`8?~yMTHg1fJml3&jvVJH;$W;)m@IW+Ofp0; z46hPfv#?&(yDjw;xff;@tv-?4oP~_Y!pkxhw{_Q?(uqBhqY8~?+2WDCddcTW+En%Y z{*Qjs?l{zc|CTJ6SDrNsB?JSkJJ_cZr5 z_l!-OILy)BKJBs>TJvKZfsFbz=1=t)%k0T)cP}z7-mfV}Lah%h%pYz%I#!+)a_VEN zrA^QqisLgh)G<-yGE>ygmj`t4R!y8{ty8V3L1!q+TPvXL(jF5QUF3AnCOR^MiM_G3li}xcW2^r|Y=Dh(ZFNq`qz!3~ik=(TdqxSd*S%s1{a>lUX{(@!@2<)2EA* zQtCl*tg2Rk%)514P*q0z&yzliY-oz+amChAwTx4`$CfXxV*+2Ajv^dpL7dZ!v@SBT>;o>+*7J|F^tMljE0iqUuglZZ~SK@6Tlo=V19i zLcd1WtMxV{qq(x}xFj)Q=<+xe;gzk&tLg1!#4m6Y4k@A^h-acGj~v&1garVbbp{q$Ix?R%QYj3uk|pPN$VN>ov8l+@5MAx literal 0 HcmV?d00001 diff --git a/locales/it_IT.po b/locales/it_IT.po new file mode 100644 index 0000000..70dff38 --- /dev/null +++ b/locales/it_IT.po @@ -0,0 +1,158 @@ +msgid "" +msgstr "" +"Project-Id-Version: urbackup 0.4.0\n" +"Language: it_IT\n" +"Content-Type: text/plain; charset=UTF-8\n" + +msgid "UrBackup" +msgstr "UrBackup" + +msgid "UrBackup server" +msgstr "Server UrBackup" + +msgid "UrBackup servers" +msgstr "Server UrBackup" + +msgid "UrBackup configuration" +msgstr "Configurazione UrBackup" + +msgid "UrBackup rights" +msgstr "Diritti UrBackup" + +msgid "No UrBackup server linked." +msgstr "Nessun server UrBackup collegato." + +msgid "UrBackup server selection" +msgstr "Selezione server UrBackup" + +msgid "Root location ID" +msgstr "ID location principale" + +msgid "Asset location ID" +msgstr "ID location asset" + +msgid "The asset is in a sub-location. The plugin will use the server assigned to the root location." +msgstr "L'asset è in una sotto-location. Il plugin userà il server della location principale." + +msgid "No UrBackup server available for the root location of this asset." +msgstr "Nessun server UrBackup disponibile per la location principale di questo asset." + +msgid "Available servers for root location" +msgstr "Server disponibili per la location principale" + +msgid "Connect" +msgstr "Collega" + +msgid "Disconnect" +msgstr "Disconnetti" + +msgid "UrBackup status" +msgstr "Stato UrBackup" + +msgid "Linked server" +msgstr "Server collegato" + +msgid "Client name" +msgstr "Nome client" + +msgid "Client IP address" +msgstr "Indirizzo IP client" + +msgid "Client version" +msgstr "Versione client" + +msgid "Online" +msgstr "Online" + +msgid "Offline" +msgstr "Offline" + +msgid "State" +msgstr "Stato" + +msgid "Actions" +msgstr "Azioni" + +msgid "Info / Log" +msgstr "Info / Log" + +msgid "Create client in UrBackup" +msgstr "Crea client in UrBackup" + +msgid "Backup commands" +msgstr "Comandi backup" + +msgid "Incremental file backup" +msgstr "Backup file incrementale" + +msgid "Full file backup" +msgstr "Backup file completo" + +msgid "Incremental image backup" +msgstr "Backup immagine incrementale" + +msgid "Full image backup" +msgstr "Backup immagine completo" + +msgid "Internet mode" +msgstr "Modalità Internet" + +msgid "Default directories" +msgstr "Directory predefinite" + +msgid "Recent backups" +msgstr "Backup recenti" + +msgid "Client logs" +msgstr "Log client" + +msgid "Delete client from UrBackup server" +msgstr "Elimina client dal server UrBackup" + +msgid "The client deletion will be queued on the UrBackup server and may require up to 24 hours." +msgstr "L'eliminazione del client verrà messa in coda sul server UrBackup e potrebbe richiedere fino a 24 ore." + +msgid "API connection status" +msgstr "Stato connessione API" + +msgid "API connection OK" +msgstr "Connessione API OK" + +msgid "API connection failed" +msgstr "Connessione API fallita" + +msgid "Server unreachable" +msgstr "Server irraggiungibile" + +msgid "No IP address configured" +msgstr "Nessun indirizzo IP configurato" + +msgid "Checking..." +msgstr "Verifica in corso..." + +msgid "Click Save to test connection" +msgstr "Clicca \"Salva\" per testare la connessione" + +msgid "Linked clients" +msgstr "Clienti collegati" + +msgid "Unlinked clients" +msgstr "Clienti non collegati" + +msgid "No linked assets" +msgstr "Nessun asset collegato" + +msgid "No unlinked clients found on UrBackup server" +msgstr "Nessun cliente non collegato trovato sul server UrBackup" + +msgid "API connection not working. Save server to test connection." +msgstr "Connessione API non funzionante. Salva il server per testare la connessione." + +msgid "Status" +msgstr "Stato" + +msgid "Last backup" +msgstr "Ultimo backup" + +msgid "Show" +msgstr "Mostra" \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..f1f8001 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,12 @@ +parameters: + level: max + paths: + - src/ + bootstrapFiles: + - vendor/autoload.php + scanDirectories: + - vendor/glpi-project/glpi/inc + ignoreErrors: + - '#Call to an undefined method#' + - '#Access to an undefined property#' + - '#Cannot call method on mixed#' diff --git a/public/css/urbackup.css b/public/css/urbackup.css new file mode 100644 index 0000000..d700831 --- /dev/null +++ b/public/css/urbackup.css @@ -0,0 +1,12 @@ +.urbackup-tab { + padding: 10px; +} + +.urbackup-tab h3 { + margin-bottom: 10px; +} + +.urbackup-tab .warning { + color: #b00; + font-weight: bold; +} diff --git a/public/js/urbackup.js b/public/js/urbackup.js new file mode 100644 index 0000000..0f57c04 --- /dev/null +++ b/public/js/urbackup.js @@ -0,0 +1,82 @@ +/** + * UrBackup API Test JavaScript + */ +(function () { + 'use strict'; + + function testApi(serverId, resultBox) { + if (!serverId || !resultBox) return; + + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/plugins/urbackup/front/server_test.ajax.php', true); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.timeout = 8000; + + xhr.onload = function () { + if (xhr.status === 200) { + try { + var data = JSON.parse(xhr.responseText); + if (data.success) { + resultBox.innerHTML = ' API connection OK'; + } else { + resultBox.innerHTML = ' API connection failed
' + (data.message || '') + ''; + } + } catch (e) { + resultBox.innerHTML = ' Error'; + } + } else { + resultBox.innerHTML = ' HTTP ' + xhr.status + ''; + } + }; + + xhr.ontimeout = function () { + resultBox.innerHTML = ' API connection failed
Connection timeout'; + }; + + xhr.onerror = function () { + resultBox.innerHTML = ' API connection failed
Network error'; + }; + + xhr.send('id=' + encodeURIComponent(serverId)); + } + + function initApiStatusCheck() { + var statusBox = document.getElementById('plugin-urbackup-api-status'); + if (!statusBox) return; + if (statusBox._initialized) return; + statusBox._initialized = true; + + var serverId = statusBox.getAttribute('data-server-id'); + if (serverId) { + testApi(serverId, statusBox); + } + } + + function initApiTestButtons() { + var buttons = document.querySelectorAll('.plugin-urbackup-test-api'); + + buttons.forEach(function (button) { + if (button._initialized) return; + button._initialized = true; + + button.addEventListener('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + + var serverId = button.getAttribute('data-server-id'); + var resultBox = document.getElementById('plugin-urbackup-api-test-result'); + + testApi(serverId, resultBox); + }); + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initApiStatusCheck); + document.addEventListener('DOMContentLoaded', initApiTestButtons); + } else { + setTimeout(initApiStatusCheck, 100); + setTimeout(initApiTestButtons, 100); + } +})(); \ No newline at end of file diff --git a/remove_deprecated_files.sh b/remove_deprecated_files.sh new file mode 100755 index 0000000..b145d5c --- /dev/null +++ b/remove_deprecated_files.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Remove deprecated front/ and ajax/ files for GLPI 11 compliance +echo "Removing deprecated front/ and ajax/ files..." +rm -f /var/www/glpi/plugins/urbackup/front/config.form.php +rm -f /var/www/glpi/plugins/urbackup/front/server.php +rm -f /var/www/glpi/plugins/urbackup/front/server.form.php +rm -f /var/www/glpi/plugins/urbackup/front/asset.form.php +rm -f /var/www/glpi/plugins/urbackup/ajax/server_test.php +rm -f /var/www/glpi/plugins/urbackup/ajax/server_clients.php +rm -f /var/www/glpi/plugins/urbackup/ajax/asset_action.php +echo "Files removed. Note: The plugin now uses Symfony Controllers." diff --git a/setup.php b/setup.php new file mode 100644 index 0000000..1f97100 --- /dev/null +++ b/setup.php @@ -0,0 +1,178 @@ + 'Profile', + ]); + Plugin::registerClass(Server::class, [ + 'linkgroup_types' => true, + 'document_types' => true, + ]); + Plugin::registerClass(ServerAsset::class); + Plugin::registerClass(PluginUrbackupMassiveAction::class); + + // Register tab on Computer and other assets + $enabled_types = Config::getEnabledItemtypes(); + if (!empty($enabled_types)) { + Plugin::registerClass(AssetTab::class, [ + 'addtabon' => $enabled_types, + ]); + } + + $PLUGIN_HOOKS['config_page']['urbackup'] = 'front/config.form.php'; + + $PLUGIN_HOOKS[Hooks::MENU_TOADD]['urbackup'] = [ + 'admin' => Server::class, + ]; + + $PLUGIN_HOOKS[Hooks::USE_MASSIVE_ACTION]['urbackup'] = true; + + $PLUGIN_HOOKS[Hooks::ADD_CSS]['urbackup'] = [ + 'public/css/urbackup.css', + ]; + + $PLUGIN_HOOKS[Hooks::ADD_JAVASCRIPT]['urbackup'] = [ + 'public/js/urbackup.js', + ]; + + $PLUGIN_HOOKS[Hooks::POST_INIT]['urbackup'] = 'plugin_urbackup_postinit'; +} + +/** + * Post init hook. + * + * @return void + */ +function plugin_urbackup_postinit(): void +{ + Config::registerAssetTabs(); +} + +/** + * Plugin version information. + * + * @return array + */ +function plugin_version_urbackup(): array +{ + return [ + 'name' => __('UrBackup', 'urbackup'), + 'version' => PLUGIN_URBACKUP_VERSION, + 'author' => 'Finstral', + 'license' => 'GPL-2.0-or-later', + 'homepage' => '', + 'requirements' => [ + 'glpi' => [ + 'min' => PLUGIN_URBACKUP_MIN_GLPI, + 'max' => PLUGIN_URBACKUP_MAX_GLPI, + ], + 'php' => [ + 'min' => '8.3.0', + ], + ], + ]; +} + +/** + * Check plugin prerequisites. + * + * @return bool + */ +function plugin_urbackup_check_prerequisites(): bool +{ + if (version_compare(GLPI_VERSION, PLUGIN_URBACKUP_MIN_GLPI, '<')) { + echo sprintf( + __('This plugin requires GLPI >= %s.', 'urbackup'), + PLUGIN_URBACKUP_MIN_GLPI + ); + return false; + } + + if (version_compare(GLPI_VERSION, PLUGIN_URBACKUP_MAX_GLPI, '>')) { + echo sprintf( + __('This plugin requires GLPI < %s.', 'urbackup'), + PLUGIN_URBACKUP_MAX_GLPI + ); + return false; + } + + if (version_compare(PHP_VERSION, '8.3.0', '<')) { + echo __('This plugin requires PHP 8.3 or higher.', 'urbackup'); + return false; + } + + return true; +} + +/** + * Check plugin config. + * + * @param bool $verbose Verbose output + * + * @return bool + */ +function plugin_urbackup_check_config(bool $verbose = false): bool +{ + return true; +} + +/** + * Plugin installation function. + * + * @return bool + */ +function plugin_urbackup_install(): bool +{ + require_once __DIR__ . '/install/install.php'; + return plugin_urbackup_install_process(); +} + +/** + * Plugin uninstallation function. + * + * @return bool + */ +function plugin_urbackup_uninstall(): bool +{ + require_once __DIR__ . '/install/uninstall.php'; + return plugin_urbackup_uninstall_process(); +} diff --git a/src/AssetTab.php b/src/AssetTab.php new file mode 100644 index 0000000..b9e4a6d --- /dev/null +++ b/src/AssetTab.php @@ -0,0 +1,913 @@ +"; + echo htmlspecialchars(__('You do not have permission to view UrBackup information.', 'urbackup')); + echo ""; + + return true; + } + + $itemtype = $item::class; + $items_id = (int) ($item->fields['id'] ?? 0); + + $link = ServerAsset::getLinkForAsset($itemtype, $items_id, true); + + echo "
"; + + if ($link === null) { + self::showNoServerLinkedBlock($item); + } else { + self::showServerLinkedBlock($item, $link); + } + + echo "
"; + + return true; + } + + /** + * Show block when no server is linked. + * + * @param CommonDBTM $item Asset item + * + * @return void + */ + private static function showNoServerLinkedBlock(CommonDBTM $item): void + { + $asset_location_id = LocationHelper::getAssetLocationId($item); + $root_location_id = LocationHelper::getRootLocationIdForAsset($item); + $is_sub_location = LocationHelper::assetIsInSubLocation($item); + $servers = LocationHelper::getAvailableServersForAsset($item); + + echo "
"; + echo htmlspecialchars(__('No UrBackup server linked.', 'urbackup')); + echo "
"; + + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + echo ""; + + if ($is_sub_location) { + echo ""; + echo ""; + echo ""; + } + + if (count($servers) === 0) { + echo ""; + echo ""; + echo ""; + echo "
" . htmlspecialchars(__('UrBackup server selection', 'urbackup')) . "
" . htmlspecialchars(__('Asset location ID', 'urbackup')) . "" . htmlspecialchars((string) $asset_location_id) . "
" . htmlspecialchars(__('Root location ID', 'urbackup')) . "" . htmlspecialchars((string) $root_location_id) . "
"; + echo htmlspecialchars( + __('The asset is in a sub-location. The plugin will use the server assigned to the root location.', 'urbackup') + ); + echo "
"; + echo "
"; + echo htmlspecialchars(__('No UrBackup server available for the root location of this asset.', 'urbackup')); + echo "
"; + echo "
"; + + return; + } + + if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(CREATE)) { + echo ""; + echo ""; + echo htmlspecialchars(__('A server is available, but you do not have permission to link this asset.', 'urbackup')); + echo ""; + echo ""; + echo ""; + + return; + } + + echo ""; + echo "" . htmlspecialchars(__('Available servers for root location', 'urbackup')) . ""; + echo ""; + + echo "
"; + echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); + echo Html::hidden('itemtype', ['value' => $item::class]); + echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]); + + Dropdown::showFromArray( + 'plugin_urbackup_servers_id', + $servers, + [ + 'value' => 0, + ] + ); + + echo Html::submit(__('Connect', 'urbackup'), [ + 'name' => 'connect', + 'class' => 'btn btn-primary', + ]); + + Html::closeForm(); + + echo ""; + echo ""; + echo ""; + } + + /** + * Show block when server is linked. + * + * @param CommonDBTM $item Asset item + * @param array $link Link row + * + * @return void + */ + private static function showServerLinkedBlock(CommonDBTM $item, array $link): void + { + $server = new Server(); + $server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0); + + if ($server_id <= 0 || !$server->getFromDB($server_id)) { + echo "
"; + echo htmlspecialchars(__('The linked UrBackup server no longer exists.', 'urbackup')); + echo "
"; + + return; + } + + $api_data = self::loadApiData($item, $server, $link); + + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + + echo "
" . htmlspecialchars(__('UrBackup status', 'urbackup')) . "
" . htmlspecialchars(__('Linked server', 'urbackup')) . "" . $server->getLink() . "" . htmlspecialchars(__('IP address', 'urbackup')) . "" . htmlspecialchars((string) $server->fields['ip_address']) . "
" . htmlspecialchars(__('UrBackup server version', 'urbackup')) . "" . htmlspecialchars((string) ($server->fields['server_version'] ?? '')) . "" . htmlspecialchars(__('Client name', 'urbackup')) . "" . htmlspecialchars(ServerAsset::getAssetName($item::class, (int) $item->fields['id'])) . "
"; + + if ($api_data['error'] !== '') { + echo "
"; + echo htmlspecialchars($api_data['error']); + echo "
"; + } + + if ($api_data['ip_warning'] !== '') { + echo "
"; + echo htmlspecialchars($api_data['ip_warning']); + echo "
"; + } + + self::showInternalTabs($item, $server, $link, $api_data); + } + + /** + * Load API data for asset tab. + * + * @param CommonDBTM $item Asset + * @param Server $server Server + * @param array $link Link + * + * @return array + */ + private static function loadApiData(CommonDBTM $item, Server $server, array $link): array + { + $client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']); + $asset_ip = ServerAsset::extractAssetIp($item); + + $data = [ + 'client_found' => false, + 'client_status' => [], + 'client_settings' => [], + 'authkey' => '', + 'recent_backups' => [], + 'logs' => [], + 'error' => '', + 'ip_warning' => '', + ]; + + try { + $api = new UrbackupApiClient($server); + $client_status = $api->getClientStatusByName($client_name); + + if ($client_status !== null) { + $data['client_found'] = true; + $data['client_status'] = $client_status; + + $urbackup_ip = (string) ( + $client_status['ip'] ?? + $client_status['ip_address'] ?? + $client_status['addr'] ?? + '' + ); + + if ($asset_ip !== '' && $urbackup_ip !== '' && $asset_ip !== $urbackup_ip) { + $data['ip_warning'] = sprintf( + __('Client name matches, but GLPI IP "%s" differs from UrBackup IP "%s".', 'urbackup'), + $asset_ip, + $urbackup_ip + ); + } + + $data['client_settings'] = $api->getClientSettings($client_name); + $data['authkey'] = $api->getClientAuthKey($client_name); + $data['recent_backups'] = $api->getRecentBackups($client_name, 10); + $data['logs'] = $api->getClientLogs($client_name, 50); + } + } catch (Throwable $e) { + $data['error'] = $e->getMessage(); + } + + return $data; + } + + /** + * Show internal sections. + * + * @param CommonDBTM $item Asset + * @param Server $server Server + * @param array $link Link + * @param array $api_data API data + * + * @return void + */ + private static function showInternalTabs( + CommonDBTM $item, + Server $server, + array $link, + array $api_data + ): void { + echo "
"; + + echo "

" . htmlspecialchars(__('State', 'urbackup')) . "

"; + self::showStateSection($server, $link, $api_data); + + echo "

" . htmlspecialchars(__('Actions', 'urbackup')) . "

"; + self::showActionsSection($item, $server, $link, $api_data); + + echo "

" . htmlspecialchars(__('Info / Log', 'urbackup')) . "

"; + self::showInfoLogSection($api_data); + + echo "
"; + } + + /** + * Show state section. + * + * @param Server $server Server + * @param array $link Link + * @param array $api_data API data + * + * @return void + */ + private static function showStateSection(Server $server, array $link, array $api_data): void + { + $status = $api_data['client_status']; + $settings = $api_data['client_settings']; + + echo ""; + echo ""; + + if (!$api_data['client_found']) { + echo ""; + echo ""; + echo ""; + echo "
" . htmlspecialchars(__('Client state', 'urbackup')) . "
"; + echo htmlspecialchars(__('Client not found on UrBackup server.', 'urbackup')); + echo "
"; + + return; + } + + $internetMode = self::extractSettingValue($settings['internet_mode_enabled'] ?? $settings['internet_mode'] ?? null, 0); + $internetModeDisplay = ((int) $internetMode === 1) + ? '' . __('Yes', 'urbackup') . '' + : '' . __('No', 'urbackup') . ''; + + $rows = [ + __('Client version', 'urbackup') => $status['client_version_string'] ?? $status['client_version'] ?? $status['version'] ?? '-', + __('Online / Offline', 'urbackup') => self::formatOnlineStatus($status), + __('Last file backup', 'urbackup') => self::formatTimestamp($status['file_lastbackup'] ?? $status['lastbackup'] ?? $status['last_file_backup'] ?? ''), + __('Last image backup', 'urbackup') => self::formatTimestamp($status['image_lastbackup'] ?? $status['lastbackup_image'] ?? $status['last_image_backup'] ?? ''), + __('Last file backup result', 'urbackup') => self::formatBoolStatus($status['file_ok'] ?? null), + __('Last image backup result', 'urbackup') => self::formatBoolStatus($status['image_ok'] ?? null), + __('Current activities', 'urbackup') => $status['status'] ?? $status['activity'] ?? '-', + __('Internet mode', 'urbackup') => $internetModeDisplay, + ]; + + foreach ($rows as $label => $value) { + $displayValue = is_array($value) ? json_encode($value) : (string) $value; + echo ""; + echo "" . htmlspecialchars((string) $label) . ""; + echo "" . $displayValue . ""; + echo ""; + } + + echo ""; + echo "" . htmlspecialchars(__('Internet authentication key', 'urbackup')) . ""; + echo ""; + $authKey = (string) $api_data['authkey']; + if ($authKey === '') { + echo '-'; + } else { + echo ''; + echo '
'; + echo ''; + echo ''; + echo '
'; + } + echo ""; + echo ""; + + echo ""; + + echo '
'; + echo '' . htmlspecialchars(__('API raw data (debug)', 'urbackup')) . ''; + echo '
';
+        echo "--- client_settings ---\n\n";
+        echo htmlspecialchars(json_encode($settings, JSON_PRETTY_PRINT));
+        echo "\n\n--- client_status ---\n\n";
+        echo htmlspecialchars(json_encode($status, JSON_PRETTY_PRINT));
+        echo "\n\n--- recent_backups ---\n\n";
+        echo htmlspecialchars(json_encode($api_data['recent_backups'] ?? [], JSON_PRETTY_PRINT));
+        echo '
'; + echo '
'; + } + + /** + * Show actions section. + * + * @param CommonDBTM $item Asset + * @param Server $server Server + * @param array $link Link + * @param array $api_data API data + * + * @return void + */ + private static function showActionsSection( + CommonDBTM $item, + Server $server, + array $link, + array $api_data + ): void { + echo ""; + echo ""; + + if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(CREATE)) { + echo ""; + echo ""; + echo ""; + echo "
" . htmlspecialchars(__('Available actions', 'urbackup')) . "
"; + echo htmlspecialchars(__('You do not have permission for UrBackup actions.', 'urbackup')); + echo "
"; + + return; + } + + if (!$api_data['client_found'] && Profile::canCurrentUser(CREATE)) { + echo ""; + echo "" . htmlspecialchars(__('Create client in UrBackup', 'urbackup')) . ""; + echo ""; + self::showActionButton($item, 'create_client', __('Create client in UrBackup', 'urbackup'), 'btn btn-primary'); + echo ""; + echo ""; + } + + if (Profile::canCurrentUser(UPDATE)) { + echo ""; + echo "" . htmlspecialchars(__('Internet mode', 'urbackup')) . ""; + echo ""; + self::showInternetModeForm($item, $api_data); + echo ""; + echo ""; + + echo ""; + echo "" . htmlspecialchars(__('Default directories', 'urbackup')) . ""; + echo ""; + self::showDefaultDirsForm($item, $api_data); + echo ""; + echo ""; + + echo ""; + echo "" . htmlspecialchars(__('Backup commands', 'urbackup')) . ""; + echo ""; + self::showActionButton($item, 'incremental_file_backup', __('Incremental file backup', 'urbackup'), 'btn btn-secondary'); + self::showActionButton($item, 'full_file_backup', __('Full file backup', 'urbackup'), 'btn btn-secondary'); + self::showActionButton($item, 'incremental_image_backup', __('Incremental image backup', 'urbackup'), 'btn btn-secondary'); + self::showActionButton($item, 'full_image_backup', __('Full image backup', 'urbackup'), 'btn btn-secondary'); + echo ""; + echo ""; + + echo ""; + echo "" . htmlspecialchars(__('Disconnect client', 'urbackup')) . ""; + echo ""; + self::showDisconnectButton($item); + echo ""; + echo ""; + } + + if (Profile::canCurrentUser(PURGE)) { + echo ""; + echo "" . htmlspecialchars(__('Delete client from UrBackup server', 'urbackup')) . ""; + echo ""; + echo "
"; + echo htmlspecialchars( + __('The client deletion will be queued on the UrBackup server and may require up to 24 hours.', 'urbackup') + ); + echo "
"; + self::showActionButton($item, 'delete_client', __('Delete client from UrBackup server', 'urbackup'), 'btn btn-danger'); + echo ""; + echo ""; + } + + echo ""; + } + + /** + * Show info/log section. + * + * @param array $api_data API data + * + * @return void + */ + private static function formatBytes(mixed $bytes): string + { + $bytes = (float) ($bytes ?? 0); + if ($bytes <= 0) { + return '-'; + } + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $i = floor(log($bytes, 1024)); + $i = min((int) $i, count($units) - 1); + return sprintf('%.1f %s', $bytes / pow(1024, $i), $units[$i]); + } + + /** + * Show info/log section. + * + * @param array $api_data API data + * + * @return void + */ + private static function showInfoLogSection(array $api_data): void + { + $recent_backups = $api_data['recent_backups'] ?? []; + usort($recent_backups, function ($a, $b) { + $tsA = (int) ($a['time'] ?? $a['backuptime'] ?? $a['backup_time'] ?? $a['created'] ?? $a['start_time'] ?? 0); + $tsB = (int) ($b['time'] ?? $b['backuptime'] ?? $b['backup_time'] ?? $b['created'] ?? $b['start_time'] ?? 0); + return $tsB - $tsA; + }); + + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + + foreach ($recent_backups as $backup) { + $incremental = (int) ($backup['incremental'] ?? 0); + $timestamp = $backup['time'] ?? $backup['backuptime'] ?? $backup['backup_time'] ?? $backup['created'] ?? $backup['start_time'] ?? 0; + $dateFormatted = self::formatTimestamp($timestamp); + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + } + + if (count($api_data['recent_backups']) === 0) { + echo ""; + } + + echo "
" . htmlspecialchars(__('Recent backups', 'urbackup')) . "
" . htmlspecialchars(__('Type')) . "" . htmlspecialchars(__('Date')) . "" . htmlspecialchars(__('Result')) . "" . htmlspecialchars(__('Size')) . "" . htmlspecialchars(__('Incremental', 'urbackup')) . "" . htmlspecialchars(__('Backup ID', 'urbackup')) . "
" . htmlspecialchars((string) ($backup['backup_type'] ?? '')) . "" . htmlspecialchars($dateFormatted) . " " . htmlspecialchars(__('Success', 'urbackup')) . "" . htmlspecialchars(self::formatBytes($backup['size'] ?? $backup['size_bytes'] ?? 0)) . "" . htmlspecialchars($incremental === 1 ? __('Yes', 'urbackup') : __('No', 'urbackup')) . "" . htmlspecialchars((string) ($backup['backupid'] ?? $backup['id'] ?? '-')) . "
"; + echo htmlspecialchars(__('No recent backup information available.', 'urbackup')); + echo "
"; + + echo "
"; + + $logs = array_reverse($api_data['logs'] ?? []); + + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + + foreach ($logs as $log) { + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + } + + if (count($api_data['logs']) === 0) { + echo ""; + } + + echo "
" . htmlspecialchars(__('Client logs', 'urbackup')) . "
" . htmlspecialchars(__('Date')) . "" . htmlspecialchars(__('Level')) . "" . htmlspecialchars(__('Message')) . "
" . htmlspecialchars(self::formatTimestamp($log['time'] ?? $log['created'] ?? '')) . "" . htmlspecialchars((string) ($log['level'] ?? $log['severity'] ?? '')) . "" . htmlspecialchars((string) ($log['message'] ?? $log['msg'] ?? $log['text'] ?? '')) . "
"; + echo htmlspecialchars(__('No client logs available.', 'urbackup')); + echo "
"; + } + + /** + * Show generic UrBackup action button. + * + * @param CommonDBTM $item Asset + * @param string $action Action + * @param string $label Button label + * @param string $class CSS class + * + * @return void + */ + private static function showActionButton(CommonDBTM $item, string $action, string $label, string $class): void + { + echo ""; + echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); + echo Html::hidden('itemtype', ['value' => $item::class]); + echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]); + echo Html::hidden('urbackup_action', ['value' => $action]); + echo Html::submit($label, [ + 'name' => 'execute', + 'class' => $class, + ]); + Html::closeForm(); + echo " "; + } + + /** + * Show internet mode form. + * + * @param CommonDBTM $item Asset + * @param array $api_data API data + * + * @return void + */ + private static function showInternetModeForm(CommonDBTM $item, array $api_data): void + { + $settings = $api_data['client_settings']; + $current = (int) self::extractSettingValue( + $settings['internet_mode_enabled'] ?? $settings['internet_mode'] ?? null, + 0 + ); + + echo ""; + echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); + echo Html::hidden('itemtype', ['value' => $item::class]); + echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]); + echo Html::hidden('urbackup_action', ['value' => 'set_internet_mode']); + echo Html::hidden('execute', ['value' => '1']); + echo Html::hidden('internet_mode', ['value' => '0']); + + echo '
'; + echo ''; + echo ''; + echo '
'; + + Html::closeForm(); + } + + /** + * Show default directories form. + * + * @param CommonDBTM $item Asset + * @param array $api_data API data + * + * @return void + */ + private static function showDefaultDirsForm(CommonDBTM $item, array $api_data): void + { + $settings = $api_data['client_settings']; + $raw = $settings['default_dirs'] ?? ''; + + $display = ''; + if (is_array($raw)) { + // API returns struct: {"use":N,"value":"paths","value_client":"","value_group":""} + $display = (string) ($raw['value'] ?? ''); + } else { + $display = (string) $raw; + } + + echo ""; + echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); + echo Html::hidden('itemtype', ['value' => $item::class]); + echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]); + echo Html::hidden('urbackup_action', ['value' => 'set_default_dirs']); + + echo "
"; + + echo '
'; + echo 'raw: ' . htmlspecialchars(json_encode($raw)); + echo '
'; + + echo Html::submit(__('Save'), [ + 'name' => 'execute', + 'class' => 'btn btn-primary', + ]); + + Html::closeForm(); + } + + /** + * Show disconnect button. + * + * @param CommonDBTM $item Asset + * + * @return void + */ + private static function showDisconnectButton(CommonDBTM $item): void + { + echo ""; + echo Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]); + echo Html::hidden('itemtype', ['value' => $item::class]); + echo Html::hidden('items_id', ['value' => (int) $item->fields['id']]); + + echo Html::submit(__('Disconnect', 'urbackup'), [ + 'name' => 'disconnect', + 'class' => 'btn btn-warning', + ]); + + Html::closeForm(); + } + + /** + * Format online status. + * + * @param array $status Status + * + * @return string + */ + private static function formatOnlineStatus(array $status): string + { + $online = $status['online'] ?? $status['is_online'] ?? null; + + return match (true) { + $online === true || $online === 1 || $online === '1' || $online === 'true' => __('Online', 'urbackup'), + $online === false || $online === 0 || $online === '0' || $online === 'false' => __('Offline', 'urbackup'), + default => '', + }; + } + + /** + * Format bool status. + * + * @param mixed $value Value + * + * @return string + */ + private static function formatBoolStatus(mixed $value): string + { + return match (true) { + $value === true || $value === 1 || $value === '1' || $value === 'true' => __('OK', 'urbackup'), + $value === false || $value === 0 || $value === '0' || $value === 'false' => __('Failed', 'urbackup'), + default => '', + }; + } + + /** + * Format timestamp-like value. + * + * @param mixed $value Timestamp + * + * @return string + */ + private static function formatTimestamp(mixed $value): string + { + if ($value === null || $value === '' || $value === 0 || $value === '0') { + return ''; + } + + $ts = (int) $value; + if ($ts > 0 && $ts < 2000000000) { + // Unix timestamp + return Html::convDateTime(date('Y-m-d H:i:s', $ts)); + } + + $str = trim((string) $value); + if ($str === '') { + return ''; + } + + // ISO date string like "2024-01-15T10:30:00" or "2024-01-15 10:30:00" + $normalized = str_replace('T', ' ', $str); + if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/', $normalized)) { + return Html::convDateTime($normalized); + } + + // Plain date like "2024-01-15" + if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $str)) { + return Html::convDate($str); + } + + return $str; + } + + public static function startBackup(CommonDBTM $item, string $type): bool + { + $link = ServerAsset::getLinkForAsset($item::class, (int) $item->fields['id'], false); + if ($link === null) { + return false; + } + + $server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0); + if ($server_id <= 0) { + return false; + } + + $server = new Server(); + if (!$server->getFromDB($server_id)) { + return false; + } + + $client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']); + if ($client_name === '') { + return false; + } + + try { + $api = new UrbackupApiClient($server); + + switch ($type) { + case 'file': + return $api->startIncrementalFileBackup($client_name); + case 'image': + return $api->startIncrementalImageBackup($client_name); + } + } catch (\Throwable $e) { + return false; + } + + return false; + } + + public static function saveInternetMode(CommonDBTM $item, bool $enabled): bool + { + $link = ServerAsset::getLinkForAsset($item::class, (int) $item->fields['id'], false); + if ($link === null) { + return false; + } + + $server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0); + if ($server_id <= 0) { + return false; + } + + $server = new Server(); + if (!$server->getFromDB($server_id)) { + return false; + } + + $client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']); + if ($client_name === '') { + return false; + } + + try { + $api = new UrbackupApiClient($server); + return $api->saveInternetMode($client_name, $enabled); + } catch (\Throwable $e) { + return false; + } + } + + public static function saveDefaultDirs(CommonDBTM $item, string $dirs): bool + { + $link = ServerAsset::getLinkForAsset($item::class, (int) $item->fields['id'], false); + if ($link === null) { + return false; + } + + $server_id = (int) ($link['plugin_urbackup_servers_id'] ?? 0); + if ($server_id <= 0) { + return false; + } + + $server = new Server(); + if (!$server->getFromDB($server_id)) { + return false; + } + + $client_name = ServerAsset::getAssetName($item::class, (int) $item->fields['id']); + if ($client_name === '') { + return false; + } + + try { + $api = new UrbackupApiClient($server); + return $api->updateClientSettings($client_name, 'default_dirs', trim($dirs)); + } catch (\Throwable $e) { + return false; + } + } +} \ No newline at end of file diff --git a/src/Command/TestApiCommand.php b/src/Command/TestApiCommand.php new file mode 100644 index 0000000..e7b8529 --- /dev/null +++ b/src/Command/TestApiCommand.php @@ -0,0 +1,56 @@ +setName('urbackup:test-api') + ->setDescription('Test UrBackup server API connection') + ->addArgument('server_id', InputArgument::REQUIRED, 'Server ID'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $server_id = (int) $input->getArgument('server_id'); + + $server = new Server(); + if (!$server->getFromDB($server_id)) { + $output->writeln("Server not found: $server_id"); + return Command::FAILURE; + } + + $output->writeln("Testing API for: " . $server->fields['name']); + $output->writeln("URL: " . $server->getWebInterfaceUrl()); + $output->writeln("Username: " . $server->fields['api_username']); + + $client = new UrbackupApiClient($server); + + try { + $result = $client->testConnection(); + + if ($result['success']) { + $output->writeln("SUCCESS: " . $result['message'] . ""); + $output->writeln("Identity: " . $result['identity']); + } else { + $output->writeln("FAILED: " . $result['message'] . ""); + } + + return $result['success'] ? Command::SUCCESS : Command::FAILURE; + } catch (\Throwable $e) { + $output->writeln("Exception: " . $e->getMessage() . ""); + return Command::FAILURE; + } + } +} diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000..679b0da --- /dev/null +++ b/src/Config.php @@ -0,0 +1,414 @@ +tableExists($table)) { + return; + } + + $existing = $DB->request([ + 'FROM' => $table, + 'WHERE' => [ + 'itemtype' => $itemtype, + ], + 'LIMIT' => 1, + ]); + + if (count($existing) > 0) { + $row = $existing->current(); + + $DB->update( + $table, + [ + 'is_active' => $is_active ? 1 : 0, + 'is_default' => $is_default ? 1 : 0, + 'date_mod' => $_SESSION['glpi_currenttime'] ?? date('Y-m-d H:i:s'), + ], + [ + 'id' => (int) $row['id'], + ] + ); + + return; + } + + $DB->insert( + $table, + [ + 'itemtype' => $itemtype, + 'is_active' => $is_active ? 1 : 0, + 'is_default' => $is_default ? 1 : 0, + 'date_creation' => $_SESSION['glpi_currenttime'] ?? date('Y-m-d H:i:s'), + 'date_mod' => $_SESSION['glpi_currenttime'] ?? date('Y-m-d H:i:s'), + ] + ); + } + + /** + * Get assettypes table name. + * + * @return string + */ + public static function getAssetTypesTable(): string + { + return 'glpi_plugin_urbackup_assettypes'; + } + + /** + * Check whether itemtype is enabled for UrBackup. + * + * @param string $itemtype Itemtype + * + * @return bool + */ + public static function isItemtypeEnabled(string $itemtype): bool + { + global $DB; + + if ($itemtype === '') { + return false; + } + + if ($itemtype === 'Computer') { + return true; + } + + $table = self::getAssetTypesTable(); + + if (!$DB->tableExists($table)) { + return false; + } + + $iterator = $DB->request([ + 'FROM' => $table, + 'WHERE' => [ + 'itemtype' => $itemtype, + 'is_active' => 1, + ], + 'LIMIT' => 1, + ]); + + return count($iterator) > 0; + } + + /** + * Get enabled itemtypes. + * + * @return array + */ + public static function getEnabledItemtypes(): array + { + global $DB; + + $types = ['Computer']; + $table = self::getAssetTypesTable(); + + if (!$DB->tableExists($table)) { + return $types; + } + + $iterator = $DB->request([ + 'FROM' => $table, + 'WHERE' => [ + 'is_active' => 1, + ], + 'ORDER' => 'itemtype', + ]); + + foreach ($iterator as $row) { + $itemtype = (string) $row['itemtype']; + + if ($itemtype !== '' && !in_array($itemtype, $types, true)) { + $types[] = $itemtype; + } + } + + return $types; + } + + /** + * Register tabs on enabled asset types. + * + * @return void + */ + public static function registerAssetTabs(): void + { + foreach (self::getEnabledItemtypes() as $itemtype) { + if (!class_exists($itemtype)) { + continue; + } + + if (!is_a($itemtype, CommonDBTM::class, true)) { + continue; + } + + \Plugin::registerClass(AssetTab::class, [ + 'addtabon' => $itemtype, + ]); + } + } + + /** + * Show config form. + * + * @param int $ID ID + * @param array $options Options + * + * @return bool + */ + public function showForm($ID, array $options = []): bool + { + Session::checkRight('config', UPDATE); + + echo ""; + echo "
"; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + + foreach (self::getConfigurableAssetTypes() as $itemtype => $label) { + $enabled = self::isItemtypeEnabled($itemtype); + $is_default = ($itemtype === 'Computer'); + $is_asset_definition = self::isAssetDefinition($itemtype); + + echo ""; + echo ""; + + if ($itemtype === 'Computer') { + echo ""; + echo ""; + } elseif ($is_asset_definition) { + // GLPI 11 Asset Definition + echo ""; + echo ""; + echo ""; + } else { + // Legacy type + echo ""; + echo ""; + echo ""; + } + + echo ""; + } + + echo ""; + echo ""; + echo ""; + + echo "
" . htmlspecialchars(__('UrBackup configuration', 'urbackup')) . "
" . htmlspecialchars(__('Asset type', 'urbackup')) . "" . htmlspecialchars(__('Enabled', 'urbackup')) . "" . htmlspecialchars(__('Default', 'urbackup')) . "" . htmlspecialchars(__('Type', 'urbackup')) . "
" . htmlspecialchars($label) . "" . htmlspecialchars(__('Always', 'urbackup')) . "" . htmlspecialchars(__('Yes', 'urbackup')) . ""; + Html::showCheckbox([ + 'name' => 'assettypes[' . htmlspecialchars($itemtype) . '][is_active]', + 'checked' => $enabled, + ]); + echo ""; + Html::showCheckbox([ + 'name' => 'assettypes[' . htmlspecialchars($itemtype) . '][is_default]', + 'checked' => $is_default, + ]); + echo "Asset Definition"; + Html::showCheckbox([ + 'name' => 'assettypes[' . htmlspecialchars($itemtype) . ']', + 'checked' => $enabled, + ]); + echo "" . ($is_default ? htmlspecialchars(__('Yes', 'urbackup')) : '') . "Legacy
"; + echo Html::submit(__('Save', 'urbackup'), [ + 'name' => 'update', + 'class' => 'btn btn-primary', + ]); + echo "
"; + echo "
"; + + Html::closeForm(); + + return true; + } + + /** + * Save configuration. + * + * @param array $input Input data + * + * @return void + */ + public static function saveConfiguration(array $input): void + { + Session::checkRight('config', UPDATE); + + $selected = $input['assettypes'] ?? []; + + foreach (self::getConfigurableAssetTypes() as $itemtype => $label) { + if ($itemtype === 'Computer') { + self::ensureAssetType('Computer', true, true); + continue; + } + + // Check if it's an Asset Definition (new format with is_active/is_default) + if (self::isAssetDefinition($itemtype)) { + $is_active = isset($selected[$itemtype]['is_active']) && (bool) $selected[$itemtype]['is_active']; + $is_default = isset($selected[$itemtype]['is_default']) && (bool) $selected[$itemtype]['is_default']; + self::ensureAssetType($itemtype, $is_active, $is_default); + } else { + // Legacy format (simple checkbox) + $is_enabled = isset($selected[$itemtype]) && (bool) $selected[$itemtype]; + self::ensureAssetType($itemtype, $is_enabled, false); + } + } + } + + /** + * Get configurable asset types (legacy + GLPI 11 Asset Definition). + * + * @return array + */ + public static function getConfigurableAssetTypes(): array + { + $types = [ + 'Computer' => Computer::getTypeName(Session::getPluralNumber()), + ]; + + // Legacy types + $known_types = [ + 'Printer', + 'Peripheral', + 'NetworkEquipment', + 'Phone', + 'Monitor', + ]; + + foreach ($known_types as $itemtype) { + if (!class_exists($itemtype)) { + continue; + } + + if (!is_a($itemtype, CommonDBTM::class, true)) { + continue; + } + + $types[$itemtype] = $itemtype::getTypeName(Session::getPluralNumber()); + } + + // GLPI 11 Asset Definition + if (class_exists(AssetDefinitionManager::class)) { + try { + $assetDefinitions = AssetDefinitionManager::getInstance()->getDefinitions(true); + foreach ($assetDefinitions as $definition) { + // Access fields directly like CommonDBTM + $system_name = $definition->fields['system_name'] ?? ''; + $name = $definition->fields['name'] ?? ''; + if (!empty($system_name)) { + $types[$system_name] = !empty($name) ? $name : $system_name; + } + } + } catch (\Throwable $e) { + // If AssetDefinitionManager fails, skip + } + } + + return $types; + } + + /** + * Check if itemtype is a GLPI 11 Asset Definition. + * + * @param string $itemtype Itemtype or system name + * + * @return bool + */ + public static function isAssetDefinition(string $itemtype): bool + { + if (!class_exists(AssetDefinitionManager::class)) { + return false; + } + + try { + $assetDefinitions = AssetDefinitionManager::getInstance()->getDefinitions(true); + foreach ($assetDefinitions as $definition) { + if (($definition->fields['system_name'] ?? '') === $itemtype) { + return true; + } + } + } catch (\Throwable $e) { + // If fails, return false + } + + return false; + } +} \ No newline at end of file diff --git a/src/Controller/AssetController.php b/src/Controller/AssetController.php new file mode 100644 index 0000000..0d8b64b --- /dev/null +++ b/src/Controller/AssetController.php @@ -0,0 +1,193 @@ +request->get('itemtype', ''); + $items_id = (int) $request->request->get('items_id', 0); + + if ($itemtype === '' || $items_id <= 0 || !class_exists($itemtype)) { + Session::addMessageAfterRedirect( + __('Invalid asset reference.', 'urbackup'), + true, + ERROR + ); + Html::back(); + } + + if (!Config::isItemtypeEnabled($itemtype)) { + Session::addMessageAfterRedirect( + __('UrBackup is not enabled for this asset type.', 'urbackup'), + true, + ERROR + ); + Html::back(); + } + + $item = new $itemtype(); + + if (!$item instanceof CommonDBTM || !$item->getFromDB($items_id)) { + Session::addMessageAfterRedirect( + __('Asset not found.', 'urbackup'), + true, + ERROR + ); + Html::back(); + } + + $action = (string) $request->request->get('urbackup_action', ''); + + switch ($action) { + case 'connect': + $server_id = (int) $request->request->get('plugin_urbackup_servers_id', 0); + if (ServerAsset::connectAssetToServer($itemtype, $items_id, $server_id)) { + Session::addMessageAfterRedirect( + __('Asset connected to UrBackup server.', 'urbackup'), + true, + INFO + ); + } + break; + + case 'disconnect': + if (ServerAsset::disconnectAsset($itemtype, $items_id)) { + Session::addMessageAfterRedirect( + __('Asset disconnected from UrBackup server.', 'urbackup'), + true, + INFO + ); + } + break; + + case 'set_internet_mode': + if (Profile::canCurrentUser(UPDATE)) { + $internet_mode = (int) $request->request->get('internet_mode', 0); + $serverAsset = new ServerAsset(); + $link = ServerAsset::getLinkForAsset($itemtype, $items_id, true); + if ($link) { + $server = new Server(); + if ($server->getFromDB((int) $link['plugin_urbackup_servers_id'])) { + $api = new UrbackupApiClient($server); + $client_name = (string) ($item->fields['name'] ?? ''); + $setting_key = $api->getInternetModeSettingKey(); + $api->changeClientSetting($client_name, $setting_key, $internet_mode); + } + } + } + break; + + case 'create_client': + if (Profile::canCurrentUser(CREATE)) { + $serverAsset = new ServerAsset(); + $link = ServerAsset::getLinkForAsset($itemtype, $items_id, true); + if ($link) { + $server = new Server(); + if ($server->getFromDB((int) $link['plugin_urbackup_servers_id'])) { + $api = new UrbackupApiClient($server); + $client_name = (string) ($item->fields['name'] ?? ''); + $api->addClient($client_name); + } + } + } + break; + + case 'incremental_file_backup': + case 'full_file_backup': + case 'incremental_image_backup': + case 'full_image_backup': + if (Profile::canCurrentUser(UPDATE)) { + $serverAsset = new ServerAsset(); + $link = ServerAsset::getLinkForAsset($itemtype, $items_id, true); + if ($link) { + $server = new Server(); + if ($server->getFromDB((int) $link['plugin_urbackup_servers_id'])) { + $api = new UrbackupApiClient($server); + $client_name = (string) ($item->fields['name'] ?? ''); + switch ($action) { + case 'incremental_file_backup': + $api->startIncrementalFileBackup($client_name); + break; + case 'full_file_backup': + $api->startFullFileBackup($client_name); + break; + case 'incremental_image_backup': + $api->startIncrementalImageBackup($client_name); + break; + case 'full_image_backup': + $api->startFullImageBackup($client_name); + break; + } + } + } + } + break; + + case 'delete_client': + if (Profile::canCurrentUser(PURGE)) { + $serverAsset = new ServerAsset(); + $link = ServerAsset::getLinkForAsset($itemtype, $items_id, true); + if ($link) { + $server = new Server(); + if ($server->getFromDB((int) $link['plugin_urbackup_servers_id'])) { + $api = new UrbackupApiClient($server); + $client_name = (string) ($item->fields['name'] ?? ''); + $api->removeClient($client_name); + } + } + } + break; + } + + Html::back(); + } + + #[Route('/plugin/urbackup/api/clients', name: 'urbackup_api_clients', methods: ['GET'])] + public function getClients(Request $request): JsonResponse + { + Session::checkLoginUser(); + + $server_id = (int) $request->query->get('server_id', 0); + + if ($server_id <= 0) { + return new JsonResponse([]); + } + + $server = new Server(); + + if (!$server->getFromDB($server_id)) { + return new JsonResponse([]); + } + + $api = new UrbackupApiClient($server); + $clients = $api->getStatus(); + + return new JsonResponse($clients); + } +} diff --git a/src/Controller/ConfigController.php b/src/Controller/ConfigController.php new file mode 100644 index 0000000..a0e5cf0 --- /dev/null +++ b/src/Controller/ConfigController.php @@ -0,0 +1,45 @@ +isMethod('POST') && $request->request->has('update')) { + Config::saveConfiguration($request->request->all()); + Html::back(); + } + + $configurableTypes = Config::getConfigurableAssetTypes(); + $enabledTypes = []; + foreach ($configurableTypes as $itemtype => $label) { + $enabledTypes[$itemtype] = Config::isItemtypeEnabled($itemtype); + } + + return $this->render('config/config.html.twig', [ + 'configurable_types' => $configurableTypes, + 'enabled_types' => $enabledTypes, + ]); + } +} diff --git a/src/Controller/ServerController.php b/src/Controller/ServerController.php new file mode 100644 index 0000000..63ea5c4 --- /dev/null +++ b/src/Controller/ServerController.php @@ -0,0 +1,109 @@ + '\d+'])] + public function showServer(int $id): void + { + $server = new Server(); + + if ($id > 0) { + $server->check($id, READ); + } else { + $server->check(-1, CREATE); + $server->getEmpty(); + } + + $server->display(['id' => $id]); + } + + #[Route('/plugin/urbackup/server/test/{id}', name: 'urbackup_server_test', methods: ['POST', 'GET'])] + public function testConnection(int $id = 0): JsonResponse + { + if (!Profile::canCurrentUser(READ)) { + return new JsonResponse([ + 'success' => false, + 'message' => 'No permission', + ], 403); + } + + if ($id <= 0) { + $id = (int) ($_POST['id'] ?? $_GET['id'] ?? 0); + } + + if ($id <= 0) { + return new JsonResponse([ + 'success' => false, + 'message' => 'Invalid server ID', + ], 400); + } + + $server = new Server(); + + if (!$server->getFromDB($id)) { + return new JsonResponse([ + 'success' => false, + 'message' => 'Server not found', + ], 404); + } + + try { + $client = new UrbackupApiClient($server); + $result = $client->testConnection(); + + $server->update([ + 'id' => $id, + 'last_api_status' => $result['success'] ? 1 : 0, + 'last_api_message' => $result['message'], + 'last_api_check' => date('Y-m-d H:i:s'), + ]); + + return new JsonResponse($result); + } catch (Throwable $e) { + return new JsonResponse([ + 'success' => false, + 'message' => $e->getMessage(), + ], 500); + } + } +} diff --git a/src/LocationHelper.php b/src/LocationHelper.php new file mode 100644 index 0000000..01c9310 --- /dev/null +++ b/src/LocationHelper.php @@ -0,0 +1,119 @@ +fields['locations_id'] ?? 0); + } + + /** + * Get root location ID from any sub-location. + * + * Required rule: + * if the computer/asset is placed in a sub-location, + * the UrBackup server reference is the one assigned to the root location. + * + * @param int $locations_id Location ID + * + * @return int + */ + public static function getRootLocationId(int $locations_id): int + { + if ($locations_id <= 0) { + return 0; + } + + $location = new Location(); + + if (!$location->getFromDB($locations_id)) { + return 0; + } + + $current_id = (int) $location->fields['id']; + $parent_id = (int) ($location->fields['locations_id'] ?? 0); + + while ($parent_id > 0) { + $parent = new Location(); + + if (!$parent->getFromDB($parent_id)) { + break; + } + + $current_id = (int) $parent->fields['id']; + $parent_id = (int) ($parent->fields['locations_id'] ?? 0); + } + + return $current_id; + } + + /** + * Get root location ID for an asset. + * + * @param CommonDBTM $item Asset item + * + * @return int + */ + public static function getRootLocationIdForAsset(CommonDBTM $item): int + { + return self::getRootLocationId(self::getAssetLocationId($item)); + } + + /** + * Get available UrBackup servers for an asset based on its root location. + * + * @param CommonDBTM $item Asset item + * + * @return array + */ + public static function getAvailableServersForAsset(CommonDBTM $item): array + { + $root_location_id = self::getRootLocationIdForAsset($item); + + if ($root_location_id <= 0) { + return []; + } + + return Server::getActiveServersForRootLocation($root_location_id); + } + + /** + * Check whether asset is in a sub-location. + * + * @param CommonDBTM $item Asset item + * + * @return bool + */ + public static function assetIsInSubLocation(CommonDBTM $item): bool + { + $location_id = self::getAssetLocationId($item); + + if ($location_id <= 0) { + return false; + } + + $root_location_id = self::getRootLocationId($location_id); + + return $root_location_id > 0 && $root_location_id !== $location_id; + } +} \ No newline at end of file diff --git a/src/MassiveAction.php b/src/MassiveAction.php new file mode 100644 index 0000000..13a1fd4 --- /dev/null +++ b/src/MassiveAction.php @@ -0,0 +1,264 @@ +getAction()) { + case self::ACTION_CONNECT_SERVER: + if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(CREATE)) { + return false; + } + + echo "
"; + echo ""; + echo ""; + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + + echo "
" . htmlspecialchars(__('Connect selected assets to an UrBackup server', 'urbackup')) . "
" . htmlspecialchars(__('UrBackup server', 'urbackup')) . ""; + + Server::dropdown([ + 'name' => 'plugin_urbackup_servers_id', + 'entity' => $_SESSION['glpiactiveentities'] ?? [], + 'entity_sons' => true, + 'condition' => ['is_active' => 1], + ]); + + echo "
"; + echo Html::submit(__('Connect', 'urbackup'), [ + 'name' => 'massiveaction', + 'class' => 'btn btn-primary', + ]); + echo "
"; + echo "
"; + + return true; + + case self::ACTION_DISCONNECT_SERVER: + if (!Profile::canCurrentUser(UPDATE)) { + return false; + } + + echo "
"; + echo ""; + echo ""; + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + + echo "
" . htmlspecialchars(__('Disconnect selected assets from UrBackup server', 'urbackup')) . "
"; + echo Html::submit(__('Disconnect', 'urbackup'), [ + 'name' => 'massiveaction', + 'class' => 'btn btn-warning', + ]); + echo "
"; + echo "
"; + + return true; + } + + return parent::showMassiveActionsSubForm($ma); + } + + /** + * Process UrBackup massive actions. + * + * @param \MassiveAction $ma Massive action object + * @param CommonDBTM $item Current item object + * @param array $ids Selected IDs + * + * @return void + */ + public static function processMassiveActionsForOneItemtype( + \MassiveAction $ma, + CommonDBTM $item, + array $ids + ): void { + $itemtype = $item->getType(); + + if (!Config::isItemtypeEnabled($itemtype)) { + foreach ($ids as $id) { + $ma->itemDone($itemtype, (int) $id, \MassiveAction::ACTION_KO); + } + + $ma->addMessage(__('UrBackup is not enabled for this asset type.', 'urbackup')); + return; + } + + switch ($ma->getAction()) { + case self::ACTION_CONNECT_SERVER: + self::processConnectServer($ma, $item, $ids); + return; + + case self::ACTION_DISCONNECT_SERVER: + self::processDisconnectServer($ma, $item, $ids); + return; + } + + parent::processMassiveActionsForOneItemtype($ma, $item, $ids); + } + + /** + * Process connect-to-server massive action. + * + * @param \MassiveAction $ma Massive action object + * @param CommonDBTM $item Current item object + * @param array $ids Selected IDs + * + * @return void + */ + private static function processConnectServer( + \MassiveAction $ma, + CommonDBTM $item, + array $ids + ): void { + if (!Profile::canCurrentUser(UPDATE) && !Profile::canCurrentUser(CREATE)) { + foreach ($ids as $id) { + $ma->itemDone($item->getType(), (int) $id, \MassiveAction::ACTION_KO); + } + + $ma->addMessage(__('You do not have permission to connect assets to UrBackup servers.', 'urbackup')); + return; + } + + $input = $ma->getInput(); + $server_id = (int) ($input['plugin_urbackup_servers_id'] ?? 0); + + if ($server_id <= 0) { + foreach ($ids as $id) { + $ma->itemDone($item->getType(), (int) $id, \MassiveAction::ACTION_KO); + } + + $ma->addMessage(__('No UrBackup server selected.', 'urbackup')); + return; + } + + $server = new Server(); + + if (!$server->getFromDB($server_id)) { + foreach ($ids as $id) { + $ma->itemDone($item->getType(), (int) $id, \MassiveAction::ACTION_KO); + } + + $ma->addMessage(__('UrBackup server not found.', 'urbackup')); + return; + } + + foreach ($ids as $id) { + $id = (int) $id; + + if (!$item->getFromDB($id)) { + $ma->itemDone($item->getType(), $id, \MassiveAction::ACTION_KO); + continue; + } + + $result = ServerAsset::connectAssetToServer( + $item->getType(), + $id, + $server_id + ); + + $ma->itemDone( + $item->getType(), + $id, + $result ? \MassiveAction::ACTION_OK : \MassiveAction::ACTION_KO + ); + } + + } + + /** + * Process disconnect-from-server massive action. + * + * @param \MassiveAction $ma Massive action object + * @param CommonDBTM $item Current item object + * @param array $ids Selected IDs + * + * @return void + */ + private static function processDisconnectServer( + \MassiveAction $ma, + CommonDBTM $item, + array $ids + ): void { + if (!Profile::canCurrentUser(UPDATE)) { + foreach ($ids as $id) { + $ma->itemDone($item->getType(), (int) $id, \MassiveAction::ACTION_KO); + } + + $ma->addMessage(__('You do not have permission to disconnect assets from UrBackup servers.', 'urbackup')); + return; + } + + foreach ($ids as $id) { + $id = (int) $id; + + if (!$item->getFromDB($id)) { + $ma->itemDone($item->getType(), $id, \MassiveAction::ACTION_KO); + continue; + } + + $result = ServerAsset::disconnectAsset( + $item->getType(), + $id + ); + + $ma->itemDone( + $item->getType(), + $id, + $result ? \MassiveAction::ACTION_OK : \MassiveAction::ACTION_KO + ); + } + + } +} \ No newline at end of file diff --git a/src/Profile.php b/src/Profile.php new file mode 100644 index 0000000..d8db92b --- /dev/null +++ b/src/Profile.php @@ -0,0 +1,246 @@ +getField('interface') === 'central') { + return self::createTabEntry(Server::getTypeName(2)); + } + return ''; + } + + public static function displayTabContentForItem( + CommonGLPI $item, + $tabnum = 1, + $withtemplate = 0 + ) { + if (!$item instanceof \Profile) { + return false; + } + + $profile = new \Profile(); + $profile->getFromDB($item->getID()); + + $rights = self::getAllRights(); + + $twig = TemplateRenderer::getInstance(); + $twig->display('@urbackup/profile.html.twig', [ + 'id' => $item->getID(), + 'profile' => $profile, + 'title' => self::getTypeName(Session::getPluralNumber()), + 'rights' => $rights, + ]); + + return true; + } + + public static function getTypeName($nb = 0): string + { + return _n('UrBackup', 'UrBackup', $nb, 'urbackup'); + } + + public static function getAllRights(): array + { + return [ + [ + 'itemtype' => Server::class, + 'label' => __('UrBackup Servers', 'urbackup'), + 'field' => 'plugin_urbackup', + ], + ]; + } + + public static function installRights(): void + { + self::registerRights(); + + $profiles_id = (int) ($_SESSION['glpiactiveprofile']['id'] ?? 0); + + if ($profiles_id > 0) { + self::setProfileRights($profiles_id, READ | UPDATE | CREATE | DELETE); + } + + global $DB; + $all_profiles = $DB->request([ + 'SELECT' => 'id', + 'FROM' => 'glpi_profiles', + ]); + + foreach ($all_profiles as $profile) { + if ($profile['id'] !== $profiles_id) { + $existing = $DB->request([ + 'FROM' => 'glpi_profilerights', + 'WHERE' => [ + 'profiles_id' => $profile['id'], + 'name' => self::$rightname, + ], + 'LIMIT' => 1, + ]); + + if (count($existing) === 0) { + $DB->insert('glpi_profilerights', [ + 'profiles_id' => $profile['id'], + 'name' => self::$rightname, + 'rights' => READ, + ]); + } + } + } + } + + public static function uninstallRights(): void + { + global $DB; + + $DB->delete('glpi_profilerights', [ + 'name' => self::$rightname, + ]); + + \ProfileRight::deleteProfileRights([self::$rightname]); + } + + public static function registerRights(): void + { + global $DB; + + $iterator = $DB->request([ + 'FROM' => 'glpi_profilerights', + 'WHERE' => ['name' => self::$rightname], + 'LIMIT' => 1, + ]); + + if (count($iterator) > 0) { + return; + } + + \ProfileRight::addProfileRights([self::$rightname]); + } + + public static function setProfileRights(int $profiles_id, int $rights): void + { + global $DB; + + if ($profiles_id <= 0) { + return; + } + + $iterator = $DB->request([ + 'FROM' => 'glpi_profilerights', + 'WHERE' => [ + 'profiles_id' => $profiles_id, + 'name' => self::$rightname, + ], + 'LIMIT' => 1, + ]); + + if (count($iterator) > 0) { + $row = $iterator->current(); + $DB->update( + 'glpi_profilerights', + ['rights' => $rights], + ['id' => (int) $row['id']] + ); + return; + } + + $DB->insert('glpi_profilerights', [ + 'profiles_id' => $profiles_id, + 'name' => self::$rightname, + 'rights' => $rights, + ]); + } + + public static function getProfileRights(int $profiles_id): int + { + global $DB; + + if ($profiles_id <= 0) { + return 0; + } + + $iterator = $DB->request([ + 'FROM' => 'glpi_profilerights', + 'WHERE' => [ + 'profiles_id' => $profiles_id, + 'name' => self::$rightname, + ], + 'LIMIT' => 1, + ]); + + if (count($iterator) === 0) { + return 0; + } + + $row = $iterator->current(); + + return (int) ($row['rights'] ?? 0); + } + + public static function canCurrentUser(int $right): bool + { + $profiles_id = (int) ($_SESSION['glpiactiveprofile']['id'] ?? 0); + + if ($profiles_id === 0) { + $user_id = (int) ($_SESSION['glpiID'] ?? 0); + if ($user_id > 0) { + global $DB; + $iterator = $DB->request([ + 'SELECT' => 'profiles_id', + 'FROM' => 'glpi_profiles_users', + 'WHERE' => [ + 'users_id' => $user_id, + 'is_dynamic' => 0, + ], + 'LIMIT' => 1, + ]); + if (count($iterator) > 0) { + $row = $iterator->current(); + $profiles_id = (int) $row['profiles_id']; + } + } + } + + if ($profiles_id > 0) { + $rights = self::getProfileRights($profiles_id); + if (($rights & $right) === $right) { + return true; + } + } + + return (bool) Session::haveRight(self::$rightname, $right); + } + + public static function initProfile($profile = null): void + { + $profile_id = 0; + + if ($profile instanceof \Profile) { + $profile_id = $profile->getID(); + } elseif (is_array($profile) && isset($profile['id'])) { + $profile_id = (int) $profile['id']; + } + + if ($profile_id > 0) { + $current_rights = self::getProfileRights($profile_id); + if ($current_rights === 0) { + self::setProfileRights($profile_id, READ); + } + } + } +} \ No newline at end of file diff --git a/src/Server.php b/src/Server.php new file mode 100644 index 0000000..d154409 --- /dev/null +++ b/src/Server.php @@ -0,0 +1,942 @@ +> + */ + public function getRights($interface = 'central'): array + { + return [ + READ => [ + 'short' => __('Read'), + 'long' => __('Read'), + ], + UPDATE => [ + 'short' => __('Update'), + 'long' => __('Update'), + ], + CREATE => [ + 'short' => __('Create'), + 'long' => __('Create'), + ], + DELETE => [ + 'short' => __('Delete'), + 'long' => __('Delete'), + ], + PURGE => [ + 'short' => __('Purge'), + 'long' => __('Purge'), + ], + ]; + } + + /** + * Check view right. + * + * @return bool + */ + public static function canView(): bool + { + return Profile::canCurrentUser(READ); + } + + /** + * Check create right. + * + * @return bool + */ + public static function canCreate(): bool + { + return Profile::canCurrentUser(CREATE); + } + + /** + * Check update right. + * + * @return bool + */ + public static function canUpdate(): bool + { + return Profile::canCurrentUser(UPDATE); + } + + /** + * Check delete right. + * + * @return bool + */ + public static function canDelete(): bool + { + return Profile::canCurrentUser(DELETE); + } + + /** + * Check purge right. + * + * @return bool + */ + public static function canPurge(): bool + { + return Profile::canCurrentUser(PURGE); + } + + /** + * Get menu name. + * + * @return string + */ + public static function getMenuName(): string + { + return self::getTypeName(Session::getPluralNumber()); + } + + /** + * Get menu content. + * + * @return array + */ + public static function getMenuContent(): array + { + $menu = []; + + if (self::canView()) { + $menu['title'] = self::getMenuName(); + $menu['page'] = self::getSearchURL(false); + $menu['icon'] = 'ti ti-server'; + + $menu['links']['search'] = self::getSearchURL(false); + + if (self::canCreate()) { + $menu['links']['add'] = self::getFormURL(false); + } + } + + return $menu; + } + + /** + * Define tabs. + * + * @param array $options Options + * + * @return array + */ + public function defineTabs($options = []): array + { + $ong = []; + + $this->addDefaultFormTab($ong); + $this->addStandardTab(ServerAsset::class, $ong, $options); + $this->addStandardTab('Log', $ong, $options); + + return $ong; + } + + /** + * Search options. + * + * @return array> + */ + public function rawSearchOptions(): array + { + $tab = []; + + $tab[] = [ + 'id' => 'common', + 'name' => __('Characteristics'), + ]; + + $tab[] = [ + 'id' => 1, + 'table' => self::getTable(), + 'field' => 'name', + 'name' => __('Name'), + 'datatype' => 'itemlink', + 'massiveaction' => false, + ]; + + $tab[] = [ + 'id' => 2, + 'table' => self::getTable(), + 'field' => 'ip_address', + 'name' => __('IP address', 'urbackup'), + 'datatype' => 'string', + ]; + + $tab[] = [ + 'id' => 3, + 'table' => self::getTable(), + 'field' => 'port', + 'name' => __('Network port', 'urbackup'), + 'datatype' => 'integer', + ]; + + $tab[] = [ + 'id' => 4, + 'table' => self::getTable(), + 'field' => 'protocol', + 'name' => __('Protocol', 'urbackup'), + 'datatype' => 'string', + ]; + + $tab[] = [ + 'id' => 5, + 'table' => self::getTable(), + 'field' => 'server_version', + 'name' => __('UrBackup server version', 'urbackup'), + 'datatype' => 'string', + ]; + + $tab[] = [ + 'id' => 6, + 'table' => Entity::getTable(), + 'field' => 'completename', + 'name' => Entity::getTypeName(1), + 'datatype' => 'dropdown', + ]; + + $tab[] = [ + 'id' => 7, + 'table' => Location::getTable(), + 'field' => 'completename', + 'name' => Location::getTypeName(1), + 'datatype' => 'dropdown', + ]; + + $tab[] = [ + 'id' => 8, + 'table' => self::getTable(), + 'field' => 'is_active', + 'name' => __('Active'), + 'datatype' => 'bool', + ]; + + $tab[] = [ + 'id' => 9, + 'table' => self::getTable(), + 'field' => 'last_api_status', + 'name' => __('Last API status', 'urbackup'), + 'datatype' => 'bool', + ]; + + $tab[] = [ + 'id' => 10, + 'table' => self::getTable(), + 'field' => 'last_api_check', + 'name' => __('Last API check', 'urbackup'), + 'datatype' => 'datetime', + ]; + + $tab[] = [ + 'id' => 11, + 'table' => self::getTable(), + 'field' => 'date_creation', + 'name' => __('Creation date'), + 'datatype' => 'datetime', + ]; + + $tab[] = [ + 'id' => 12, + 'table' => self::getTable(), + 'field' => 'date_mod', + 'name' => __('Last update'), + 'datatype' => 'datetime', + ]; + + $tab[] = [ + 'id' => 13, + 'table' => self::getTable(), + 'field' => 'id', + 'name' => __('View', 'urbackup'), + 'massiveaction' => false, + 'datatype' => 'raw', + 'searchtype' => 'view', + ]; + + return $tab; + } + + /** + * Show server form. + * + * @param int $ID ID + * @param array $options Options + * + * @return bool + */ + public function showForm($ID, array $options = []): bool + { + if ($ID > 0) { + $this->check($ID, READ); + } else { + $this->check(-1, CREATE); + $this->getEmpty(); + } + + $this->initForm($ID, $options); + $this->showFormHeader($options); + + echo ""; + echo "" . htmlspecialchars(__('Name')) . ""; + echo ""; + echo Html::input('name', [ + 'value' => $this->fields['name'] ?? '', + 'size' => 40, + ]); + echo ""; + + echo "" . htmlspecialchars(__('Active')) . ""; + echo ""; + Dropdown::showYesNo('is_active', (int) ($this->fields['is_active'] ?? 1)); + echo ""; + echo ""; + + echo ""; + echo "" . htmlspecialchars(Entity::getTypeName(1)) . ""; + echo ""; + Entity::dropdown([ + 'name' => 'entities_id', + 'value' => (int) ($this->fields['entities_id'] ?? ($_SESSION['glpiactive_entity'] ?? 0)), + ]); + echo ""; + + echo "" . htmlspecialchars(__('Recursive')) . ""; + echo ""; + Dropdown::showYesNo('is_recursive', (int) ($this->fields['is_recursive'] ?? 0)); + echo ""; + echo ""; + + echo ""; + echo "" . htmlspecialchars(Location::getTypeName(1)) . ""; + echo ""; + Location::dropdown([ + 'name' => 'locations_id', + 'value' => (int) ($this->fields['locations_id'] ?? 0), + ]); + echo "
"; + echo htmlspecialchars( + __('Associate the server with the main/root location. Assets in sub-locations will use this root location server.', 'urbackup') + ); + echo ""; + echo ""; + + echo "" . htmlspecialchars(__('Protocol', 'urbackup')) . ""; + echo ""; + Dropdown::showFromArray( + 'protocol', + [ + 'http' => 'HTTP', + 'https' => 'HTTPS', + ], + [ + 'value' => $this->fields['protocol'] ?? 'http', + ] + ); + echo ""; + echo ""; + + echo ""; + echo "" . htmlspecialchars(__('IP address', 'urbackup')) . ""; + echo ""; + echo Html::input('ip_address', [ + 'value' => $this->fields['ip_address'] ?? '', + 'size' => 40, + ]); + echo ""; + + echo "" . htmlspecialchars(__('Network port', 'urbackup')) . ""; + echo ""; + echo Html::input('port', [ + 'value' => $this->fields['port'] ?? 55414, + 'type' => 'number', + 'min' => 1, + 'max' => 65535, + ]); + echo ""; + echo ""; + + echo ""; + echo "" . htmlspecialchars(__('UrBackup server version', 'urbackup')) . ""; + echo ""; + echo Html::input('server_version', [ + 'value' => $this->fields['server_version'] ?? '', + 'size' => 30, + ]); + echo ""; + + echo "" . htmlspecialchars(__('Ignore SSL verification', 'urbackup')) . ""; + echo ""; + Dropdown::showYesNo('ignore_ssl', (int) ($this->fields['ignore_ssl'] ?? 0)); + echo ""; + echo ""; + + echo ""; + echo "" . htmlspecialchars(__('API username', 'urbackup')) . ""; + echo ""; + echo Html::input('api_username', [ + 'value' => $this->fields['api_username'] ?? '', + 'size' => 40, + 'autocomplete' => 'off', + ]); + echo ""; + + echo "" . htmlspecialchars(__('API password', 'urbackup')) . ""; + echo ""; + echo ""; + echo ""; + echo ""; + + echo ""; + echo "" . htmlspecialchars(__('Comments')) . ""; + echo ""; + echo ""; + echo ""; + echo ""; + + if ($ID > 0) { + echo ""; + echo "" . htmlspecialchars(__('UrBackup web interface', 'urbackup')) . ""; + echo ""; + + $url = $this->getWebInterfaceUrl(); + + if ($url !== '#') { + echo "
"; + echo htmlspecialchars(__('Open UrBackup interface', 'urbackup')); + echo ""; + } else { + echo htmlspecialchars(__('No URL available', 'urbackup')); + } + + echo ""; + echo ""; + +$apiStatus = (int) ($this->fields['last_api_status'] ?? 0); + echo ""; + echo "" . htmlspecialchars(__('API connection status', 'urbackup')) . "
" . htmlspecialchars(__('Click Save to test connection', 'urbackup')) . ""; + echo ""; + if ($apiStatus === 1) { + echo ' ' . htmlspecialchars(__('API connection OK', 'urbackup')) . ''; + } else { + echo ' ' . htmlspecialchars(__('API connection failed', 'urbackup')) . ''; + if (!empty($this->fields['last_api_message'])) { + echo '
' . htmlspecialchars((string) $this->fields['last_api_message']) . ''; + } + } + echo ""; + echo ""; + } + + $this->showFormButtons($options); + + return true; + } + + /** + * Get web interface URL. + * + * @return string + */ + public function getWebInterfaceUrl(): string + { + $protocol = (string) ($this->fields['protocol'] ?? 'http'); + $ip = (string) ($this->fields['ip_address'] ?? ''); + $port = (int) ($this->fields['port'] ?? 55414); + + if ($ip === '') { + return '#'; + } + + return sprintf('%s://%s:%d', $protocol, $ip, $port); + } + + /** + * Get active servers assigned to a root location. + * + * @param int $locations_id Root location ID + * + * @return array + */ + public static function getActiveServersForRootLocation(int $locations_id): array + { + global $DB; + + $servers = []; + + if ($locations_id <= 0) { + return $servers; + } + + if (!$DB->tableExists(self::getTable())) { + return $servers; + } + + $iterator = $DB->request([ + 'FROM' => self::getTable(), + 'WHERE' => [ + 'locations_id' => $locations_id, + 'is_active' => 1, + ], + 'ORDER' => 'name', + ]); + + foreach ($iterator as $row) { + $servers[(int) $row['id']] = (string) $row['name']; + } + + return $servers; + } + + public function prepareInputForAdd(mixed $input): mixed + { + return $this->prepareInputForUpdate($input); + } + + public function prepareInputForUpdate(mixed $input): mixed + { + if (!is_array($input)) { + return $input; + } + + if (!empty($input['id']) && (int) $input['id'] > 0) { + $server = new self(); + if ($server->getFromDB((int) $input['id'])) { + $serverFields = $server->fields; + $ip = $input['ip_address'] ?? $serverFields['ip_address'] ?? ''; + $port = $input['port'] ?? $serverFields['port'] ?? 55414; + $protocol = $input['protocol'] ?? $serverFields['protocol'] ?? 'http'; + $apiUsername = $input['api_username'] ?? $serverFields['api_username'] ?? ''; + $apiPassword = $input['api_password'] ?? $serverFields['api_password'] ?? ''; + $ignoreSsl = $input['ignore_ssl'] ?? $serverFields['ignore_ssl'] ?? 0; + + if ($ip !== '') { + $tmpServer = new self(); + $tmpServer->fields = [ + 'id' => (int) $input['id'], + 'ip_address' => $ip, + 'port' => $port, + 'protocol' => $protocol, + 'api_username' => $apiUsername, + 'api_password' => $apiPassword, + 'ignore_ssl' => $ignoreSsl, + ]; + + try { + $client = new UrbackupApiClient($tmpServer); + $result = $client->testConnection(); + $input['last_api_status'] = $result['success'] ? 1 : 0; + $input['last_api_message'] = $result['message'] ?? ''; + $input['last_api_check'] = date('Y-m-d H:i:s'); + } catch (\Throwable $e) { + $input['last_api_status'] = 0; + $input['last_api_message'] = $e->getMessage(); + $input['last_api_check'] = date('Y-m-d H:i:s'); + } + } + } + } + + return $input; + } + + public function testApiConnection(): array + { + $ip = (string) ($this->fields['ip_address'] ?? ''); + + if ($ip === '') { + return [ + 'status' => 'no_ip', + 'html' => '' . htmlspecialchars(__('No IP address configured', 'urbackup')) . '', + ]; + } + + try { + $client = new UrbackupApiClient($this); + $result = $client->testConnection(); + + $this->update([ + 'id' => (int) $this->fields['id'], + 'last_api_status' => $result['success'] ? 1 : 0, + 'last_api_message' => $result['message'] ?? '', + 'last_api_check' => date('Y-m-d H:i:s'), + ]); + + if ($result['success']) { + return [ + 'status' => 'ok', + 'html' => ' ' . + htmlspecialchars(__('API connection OK', 'urbackup')) . '', + ]; + } + + return [ + 'status' => 'failed', + 'html' => ' ' . + htmlspecialchars(__('API connection failed', 'urbackup')) . '
' . + '' . htmlspecialchars($result['message'] ?? '') . '', + ]; + } catch (\Throwable $e) { + $message = $e->getMessage(); + $isUnreachable = $this->isNetworkError($message); + + return [ + 'status' => $isUnreachable ? 'unreachable' : 'failed', + 'html' => '' . + ' ' . + htmlspecialchars($isUnreachable ? __('Server unreachable', 'urbackup') : __('API connection failed', 'urbackup')) . + '
' . + '' . htmlspecialchars($message) . '', + ]; + } + } + + private function isNetworkError(string $message): bool + { + $networkKeywords = [ + 'timeout', + 'could not resolve host', + 'couldn\'t connect to host', + 'connection refused', + 'connection timed out', + 'network is unreachable', + 'no route to host', + 'ssl', + 'certificate', + 'curl error', + 'request failed', + 'returned HTTP status', + 'returned non-JSON response', + 'problem with the ssl certificate', + 'ssl certificate problem', + 'ssl connect error', + 'ssl wrong version', + ]; + + $lowerMessage = strtolower($message); + + foreach ($networkKeywords as $keyword) { + if (str_contains($lowerMessage, strtolower($keyword))) { + return true; + } + } + + if (preg_match('/http status [45]\d{2}/', $lowerMessage)) { + return true; + } + + return false; + } + + private static function renderOnlineBadge(mixed $online, mixed $statusString): string + { + $online = match (true) { + $online === true || $online === 1 || $online === '1' || $online === 'true' => 1, + $online === false || $online === 0 || $online === '0' || $online === 'false' => 0, + default => null, + }; + + $parts = []; + + if ($online === 1) { + $parts[] = ' ' + . htmlspecialchars(__('Online', 'urbackup')) . ''; + } elseif ($online === 0) { + $parts[] = ' ' + . htmlspecialchars(__('Offline', 'urbackup')) . ''; + } else { + $parts[] = '-'; + } + + $statusString = is_string($statusString) ? trim((string) $statusString) : ''; + if ($statusString !== '') { + $badgeClass = 'secondary'; + if (strtolower($statusString) === 'ok') { + $badgeClass = 'success'; + } elseif (in_array(strtolower($statusString), ['minor_problems', 'minor problems'], true)) { + $badgeClass = 'warning'; + } elseif (in_array(strtolower($statusString), ['major_problems', 'major problems', 'error'], true)) { + $badgeClass = 'danger'; + } elseif (strtolower($statusString) === 'paused') { + $badgeClass = 'secondary'; + } + $parts[] = '' + . htmlspecialchars($statusString) . ''; + } + + return implode(' ', $parts); + } + + private static function formatLastBackup(mixed $value): string + { + if ($value === null || $value === '' || $value === 0 || $value === '0') { + return ''; + } + + if (is_numeric($value)) { + $timestamp = (int) $value; + if ($timestamp > 0 && $timestamp < 2000000000) { + return date('Y-m-d H:i:s', $timestamp); + } + return (string) $value; + } + + return (string) $value; + } + + public static function showLinkedClientsTab(Server $server): void + { + global $DB; + + $apiStatus = (int) ($server->fields['last_api_status'] ?? 0); + + if ($apiStatus !== 1) { + echo '
'; + echo htmlspecialchars(__('API connection not working. Save server to test connection.', 'urbackup')); + echo '
'; + return; + } + + $iterator = $DB->request([ + 'FROM' => 'glpi_plugin_urbackup_serverassets', + 'WHERE' => [ + 'plugin_urbackup_servers_id' => (int) $server->fields['id'], + ], + ]); + + $linkedAssets = []; + foreach ($iterator as $row) { + $linkedAssets[] = $row; + } + + if (count($linkedAssets) === 0) { + echo '
'; + echo htmlspecialchars(__('No linked assets', 'urbackup')); + echo '
'; + return; + } + + try { + $client = new UrbackupApiClient($server); + $urbackupClients = $client->getStatus(); + + if (empty($urbackupClients)) { + echo '
'; + echo htmlspecialchars(__('No clients found on UrBackup server', 'urbackup')); + echo '
'; + return; + } + } catch (\Throwable $e) { + echo '
'; + echo 'API Error: ' . htmlspecialchars($e->getMessage()); + echo '
'; + return; + } + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + foreach ($linkedAssets as $link) { + $glpiItem = null; + if (!empty($link['itemtype']) && !empty($link['items_id'])) { + $itemClass = $link['itemtype']; + if (class_exists($itemClass)) { + $glpiItem = new $itemClass(); + $glpiItem->getFromDB((int) $link['items_id']); + } + } + + $clientName = $glpiItem ? ($glpiItem->fields['name'] ?? '') : ''; + $urbackupClient = null; + + foreach ($urbackupClients as $uc) { + $ucName = (string) ($uc['name'] ?? $uc['clientname'] ?? $uc['hostname'] ?? ''); + if (strcasecmp($ucName, $clientName) === 0) { + $urbackupClient = $uc; + break; + } + } + + $online = $urbackupClient ? ($urbackupClient['online'] ?? null) : null; + $apiStatusString = $urbackupClient ? ($urbackupClient['status'] ?? '') : ''; + $statusHtml = self::renderOnlineBadge($online, $apiStatusString); + + $lastBackupRaw = $urbackupClient + ? ($urbackupClient['lastbackup'] ?? $urbackupClient['lastbackup_time'] ?? $urbackupClient['last_backup'] ?? $urbackupClient['last_backup_time'] ?? $urbackupClient['file_lastbackup'] ?? $urbackupClient['image_lastbackup'] ?? '') + : ''; + $lastBackup = self::formatLastBackup($lastBackupRaw); + + $clientIp = $urbackupClient + ? ($urbackupClient['client_ip'] ?? $urbackupClient['ip'] ?? $urbackupClient['ip_address'] ?? $urbackupClient['clientaddress'] ?? '') + : ''; + + $clientVersion = $urbackupClient ? ($urbackupClient['client_version_string'] ?? $urbackupClient['client_version'] ?? '-') : '-'; + $urbackupClientName = $urbackupClient ? ($urbackupClient['name'] ?? '-') : '-'; + + $itemTypeLabel = !empty($link['itemtype']) ? (class_exists($link['itemtype']) ? $link['itemtype']::getTypeName(1) : $link['itemtype']) : ''; + $itemUrl = $glpiItem ? $glpiItem->getLinkURL() : ''; + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + + echo ''; + echo '
' . htmlspecialchars(__('Asset', 'urbackup')) . '' . htmlspecialchars(__('Name', 'urbackup')) . '' . htmlspecialchars(__('Client name', 'urbackup')) . '' . htmlspecialchars(__('Version', 'urbackup')) . '' . htmlspecialchars(__('Status', 'urbackup')) . '' . htmlspecialchars(__('Last backup', 'urbackup')) . '' . htmlspecialchars(__('IP address', 'urbackup')) . '
' . htmlspecialchars($itemTypeLabel . ' #' . $link['items_id']) . '' . ($itemUrl ? '' . htmlspecialchars($clientName) . '' : htmlspecialchars($clientName)) . '' . htmlspecialchars($urbackupClientName) . '' . htmlspecialchars($clientVersion) . '' . $statusHtml . '' . htmlspecialchars($lastBackup ?: '-') . '' . htmlspecialchars($clientIp ?: '-') . '
'; + } + + public static function showUnlinkedClientsTab(Server $server): void + { + $apiStatus = (int) ($server->fields['last_api_status'] ?? 0); + + if ($apiStatus !== 1) { + echo '
'; + echo htmlspecialchars(__('API connection not working. Save server to test connection.', 'urbackup')); + echo '
'; + return; + } + + global $DB; + + $iterator = $DB->request([ + 'FROM' => 'glpi_plugin_urbackup_serverassets', + ]); + + $linkedNames = []; + foreach ($iterator as $row) { + $assetName = ServerAsset::getAssetName($row['itemtype'], (int) $row['items_id']); + if ($assetName !== '') { + $linkedNames[] = strtolower($assetName); + } + } + + try { + $client = new UrbackupApiClient($server); + $urbackupClients = $client->getStatus(); + } catch (\Throwable $e) { + echo '
'; + echo htmlspecialchars($e->getMessage()); + echo '
'; + return; + } + + $unlinkedClients = []; + foreach ($urbackupClients as $uc) { + $name = strtolower((string) ($uc['name'] ?? $uc['clientname'] ?? $uc['hostname'] ?? '')); + if (!in_array($name, $linkedNames, true)) { + $unlinkedClients[] = $uc; + } + } + + if (count($unlinkedClients) === 0) { + echo '
'; + echo htmlspecialchars(__('No unlinked clients found on UrBackup server', 'urbackup')); + echo '
'; + return; + } + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + + foreach ($unlinkedClients as $uc) { + $online = $uc['online'] ?? null; + $apiStatusString = $uc['status'] ?? ''; + $statusHtml = self::renderOnlineBadge($online, $apiStatusString); + + $lastBackupRaw = $uc['lastbackup'] ?? $uc['lastbackup_time'] ?? $uc['last_backup'] ?? $uc['last_backup_time'] ?? $uc['file_lastbackup'] ?? $uc['image_lastbackup'] ?? ''; + $lastBackup = self::formatLastBackup($lastBackupRaw); + + $clientIp = $uc['client_ip'] ?? $uc['ip'] ?? $uc['ip_address'] ?? $uc['clientaddress'] ?? ''; + $clientVersion = $uc['client_version_string'] ?? $uc['client_version'] ?? '-'; + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + + echo ''; + echo '
' . htmlspecialchars(__('Name', 'urbackup')) . '' . htmlspecialchars(__('Version', 'urbackup')) . '' . htmlspecialchars(__('Status', 'urbackup')) . '' . htmlspecialchars(__('Last backup', 'urbackup')) . '' . htmlspecialchars(__('IP address', 'urbackup')) . '
' . htmlspecialchars((string) ($uc['name'] ?? 'Unknown')) . '' . htmlspecialchars($clientVersion) . '' . $statusHtml . '' . htmlspecialchars($lastBackup ?: '-') . '' . htmlspecialchars($clientIp ?: '-') . '
'; + } +} \ No newline at end of file diff --git a/src/ServerAsset.php b/src/ServerAsset.php new file mode 100644 index 0000000..56eb4d8 --- /dev/null +++ b/src/ServerAsset.php @@ -0,0 +1,291 @@ +getFromDB($items_id)) { + return false; + } + + $table = self::getTable(); + $date = $_SESSION['glpi_currenttime'] ?? date('Y-m-d H:i:s'); + + $existing = self::getLinkForAsset($itemtype, $items_id, false); + + if ($existing !== null) { + return $DB->update( + $table, + [ + 'plugin_urbackup_servers_id' => $server_id, + 'client_name' => (string) ($item->fields['name'] ?? ''), + 'client_ip' => self::extractAssetIp($item), + 'is_active' => 1, + 'date_mod' => $date, + ], + [ + 'id' => (int) $existing['id'], + ] + ); + } + + return $DB->insert( + $table, + [ + 'plugin_urbackup_servers_id' => $server_id, + 'itemtype' => $itemtype, + 'items_id' => $items_id, + ] + ); + } + + /** + * Disconnect asset from server. + * + * @param string $itemtype Itemtype + * @param int $items_id Item ID + * + * @return bool + */ + public static function disconnectAsset(string $itemtype, int $items_id): bool + { + global $DB; + + if (!Profile::canCurrentUser(UPDATE)) { + return false; + } + + $link = self::getLinkForAsset($itemtype, $items_id, true); + + if ($link === null) { + return true; + } + + return $DB->delete( + self::getTable(), + [ + 'id' => (int) $link['id'], + ] + ); + } + + /** + * Get active link for asset. + * + * @param string $itemtype Itemtype + * @param int $items_id Item ID + * @param bool $active_only Active only + * + * @return array|null + */ + public static function createForAsset( + string $itemtype, + int $items_id, + int $servers_id + ): bool { + global $DB; + + $item = getItemForItemtype($itemtype); + if (!$item || !$item->getFromDB($items_id)) { + return false; + } + + $result = $DB->insert(self::getTable(), [ + 'plugin_urbackup_servers_id' => $servers_id, + 'itemtype' => $itemtype, + 'items_id' => $items_id, + ]); + + return $result; + } + + public static function getAssetName(string $itemtype, int $items_id): string + { + $item = getItemForItemtype($itemtype); + if (!$item || !$item->getFromDB($items_id)) { + return ''; + } + + return (string) ($item->fields['name'] ?? ''); + } + + public static function getLinkForAsset( + string $itemtype, + int $items_id, + bool $active_only = true + ): ?array { + global $DB; + + $iterator = $DB->request([ + 'FROM' => self::getTable(), + 'WHERE' => [ + 'itemtype' => $itemtype, + 'items_id' => $items_id, + ], + 'LIMIT' => 1, + ]); + + if (count($iterator) === 0) { + return null; + } + + return $iterator->current(); + } + + /** + * Extract asset IP address. + * + * @param CommonDBTM $item Item + * + * @return string + */ + public static function extractAssetIp(CommonDBTM $item): string + { + if (!isset($item->fields['ip_address']) || $item->fields['ip_address'] === '') { + return ''; + } + + $ip = $item->fields['ip_address']; + + if (is_array($ip)) { + return $ip[0] ?? ''; + } + + return (string) $ip; + } + +/** + * Display tab content for item. + * + * @param CommonGLPI $item Server item + * @param int $tabnum Tab number + * @param int $withtemplate Template + * + * @return bool + */ + public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0): bool + { + global $DB; + + if (!$item instanceof Server) { + return false; + } + + echo "
"; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + + $iterator = $DB->request([ + 'FROM' => self::getTable(), + 'WHERE' => [ + 'plugin_urbackup_servers_id' => (int) $item->fields['id'], + 'is_active' => 1, + ], + 'ORDER' => 'client_name', + ]); + + foreach ($iterator as $row) { + $asset_label = (string) $row['client_name']; + $itemtype = (string) $row['itemtype']; + $items_id = (int) $row['items_id']; + + if (class_exists($itemtype)) { + $asset = new $itemtype(); + + if ($asset instanceof CommonDBTM && $asset->getFromDB($items_id)) { + $asset_label = $asset->getLink(); + } + } + + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + echo ""; + } + + echo "
" . htmlspecialchars(__('Linked assets', 'urbackup')) . "
" . htmlspecialchars(__('Asset', 'urbackup')) . "" . htmlspecialchars(__('Type')) . "" . htmlspecialchars(__('IP address', 'urbackup')) . "" . htmlspecialchars(__('Last file backup', 'urbackup')) . "" . htmlspecialchars(__('Last image backup', 'urbackup')) . "
" . $asset_label . "" . htmlspecialchars($itemtype) . "" . htmlspecialchars((string) $row['client_ip']) . "" . htmlspecialchars((string) $row['last_file_backup']) . "" . htmlspecialchars((string) $row['last_image_backup']) . "
"; + echo "
"; + + return true; + } +} \ No newline at end of file diff --git a/src/UrbackupApiClient.php b/src/UrbackupApiClient.php new file mode 100644 index 0000000..f6727ed --- /dev/null +++ b/src/UrbackupApiClient.php @@ -0,0 +1,766 @@ +server = $server; + $this->base_url = rtrim($server->getWebInterfaceUrl(), '/') . '/x'; + $this->username = (string) ($server->fields['api_username'] ?? ''); + $this->password = (string) ($server->fields['api_password'] ?? ''); + $this->ignore_ssl = ((int) ($server->fields['ignore_ssl'] ?? 0)) === 1; + $this->server_version = (string) ($server->fields['server_version'] ?? ''); + $this->is_version_2_5_or_higher = $this->detectVersion2_5OrHigher(); + } + + /** + * Detect if server version is 2.5 or higher. + * + * @return bool + */ + private function detectVersion2_5OrHigher(): bool + { + if ($this->server_version === '') { + return false; + } + + $parts = explode('.', $this->server_version); + if (count($parts) < 2) { + return false; + } + + $major = (int) $parts[0]; + $minor = (int) $parts[1]; + + if ($major > 2) { + return true; + } + if ($major === 2 && $minor >= 5) { + return true; + } + + return false; + } + + /** + * Get internet mode setting key based on server version. + * + * @return string + */ + public function getInternetModeSettingKey(): string + { + return $this->is_version_2_5_or_higher ? 'internet_mode_enabled' : 'internet_mode'; + } + + /** + * Extract setting value from 2.5+ structured format or simple value. + * + * @param mixed $setting Setting value + * @param mixed $default Default value + * + * @return mixed + */ + public function extractSettingValue(mixed $setting, mixed $default = null): mixed + { + if (is_array($setting) && array_key_exists('value', $setting)) { + return $setting['value']; + } + + return $setting ?? $default; + } + + /** + * Test API connection. + * + * @return array + */ + public function testConnection(): array + { + try { + $this->login(); + $identity = $this->getServerIdentity(); + + return [ + 'success' => true, + 'message' => $identity !== '' + ? sprintf(__('Connection successful. Server identity: %s', 'urbackup'), $identity) + : __('Connection successful.', 'urbackup'), + 'identity' => $identity, + ]; + } catch (RuntimeException $e) { + return [ + 'success' => false, + 'message' => $e->getMessage(), + 'identity' => '', + ]; + } + } + + /** + * Login to UrBackup web API. + * + * @return bool + */ + public function login(): bool + { + if ($this->logged_in) { + return true; + } + + $login = $this->request('login', [], 'POST', false); + + if (!$login || !isset($login['success']) || $login['success'] !== true) { + $salt = $this->request('salt', ['username' => $this->username], 'POST', false); + + if (!$salt || !isset($salt['ses']) || $salt['ses'] === '') { + if (isset($salt['error']) && $salt['error'] === 1) { + throw new RuntimeException(__('Username does not exist on UrBackup server.', 'urbackup')); + } + throw new RuntimeException(__('Unable to get salt from UrBackup server.', 'urbackup')); + } + + $this->session = (string) $salt['ses']; + + if (isset($salt['salt'])) { + $password_md5 = $this->buildPasswordHash($this->password, $salt); + + $login = $this->request('login', [ + 'username' => $this->username, + 'password' => $password_md5, + 'ses' => $this->session, + ], 'POST', false); + + if (!$login || !isset($login['success']) || $login['success'] !== true) { + throw new RuntimeException(__('Unable to authenticate against UrBackup server. Password may be wrong.', 'urbackup')); + } + + $this->logged_in = true; + return true; + } + + throw new RuntimeException(__('Salt response missing salt field.', 'urbackup')); + } + + $this->session = (string) ($login['session'] ?? ''); + $this->logged_in = true; + return true; + } + + /** + * Build password hash for authentication. + * + * @param string $password Plain password + * @param array $salt Salt response from server + * + * @return string + */ + private function buildPasswordHash(string $password, array $salt): string + { + $salt_str = (string) ($salt['salt'] ?? ''); + $rnd = (string) ($salt['rnd'] ?? ''); + $pbkdf2_rounds = (int) ($salt['pbkdf2_rounds'] ?? 0); + + $passwordMd5Bin = md5($salt_str . $password, true); + $passwordMd5 = bin2hex($passwordMd5Bin); + + if ($pbkdf2_rounds > 0 && function_exists('hash_pbkdf2')) { + $passwordMd5 = hash_pbkdf2( + 'sha256', + $passwordMd5Bin, + $salt_str, + $pbkdf2_rounds, + 0, + false + ); + } + + return md5($rnd . $passwordMd5); + } + + /** + * Get server identity. + * + * @return string + */ + public function getServerIdentity(): string + { + $data = $this->apiAction('server_identity'); + + if (isset($data['server_identity'])) { + return (string) $data['server_identity']; + } + + if (isset($data['identity'])) { + return (string) $data['identity']; + } + + if (isset($data['name'])) { + return (string) $data['name']; + } + + return ''; + } + + /** + * Get all client statuses. + * + * @return array> + */ + public function getStatus(): array + { + $data = $this->apiAction('status'); + + if (isset($data['status']) && is_array($data['status'])) { + return array_values($data['status']); + } + + if (isset($data['clients']) && is_array($data['clients'])) { + return array_values($data['clients']); + } + + if (array_is_list($data)) { + return $data; + } + + return []; + } + + /** + * Get client status by GLPI asset name. + * + * @param string $client_name Client name + * + * @return array|null + */ + public function getClientStatusByName(string $client_name): ?array + { + foreach ($this->getStatus() as $client) { + $name = (string) ($client['name'] ?? $client['clientname'] ?? $client['hostname'] ?? ''); + + if (strcasecmp($name, $client_name) === 0) { + return $client; + } + } + + return null; + } + + /** + * Get client ID by name. + * + * @param string $client_name Client name + * + * @return int + */ + public function getClientIdByName(string $client_name): int + { + $client = $this->getClientStatusByName($client_name); + + if ($client === null) { + return 0; + } + + return (int) ($client['id'] ?? $client['clientid'] ?? $client['client_id'] ?? 0); + } + + /** + * Get client settings. + * + * @param string $client_name Client name + * + * @return array + */ + public function getClientSettings(string $client_name): array + { + $client_id = $this->getClientIdByName($client_name); + + if ($client_id <= 0) { + return []; + } + + $data = $this->apiAction('settings', [ + 'sa' => 'clientsettings', + 't_clientid' => $client_id, + ]); + + if (isset($data['settings']) && is_array($data['settings'])) { + return $data['settings']; + } + + return $data; + } + + /** + * Change a client setting. + * + * @param string $client_name Client name + * @param string $key Setting key + * @param mixed $value New value + * + * @return bool + */ + public function changeClientSetting(string $client_name, string $key, mixed $value): bool + { + $client_id = $this->getClientIdByName($client_name); + + if ($client_id <= 0) { + return false; + } + + $data = $this->apiAction('settings', [ + 'sa' => 'clientsettings_save', + 't_clientid' => $client_id, + 'overwrite' => 'true', + $key => (string) $value, + ]); + + return $this->responseIsSuccess($data); + } + + public function updateClientSettings(string $client_name, string $key, string $value): bool + { + $client_id = $this->getClientIdByName($client_name); + + if ($client_id <= 0) { + return false; + } + + $data = $this->apiAction('settings', [ + 'sa' => 'clientsettings_save', + 't_clientid' => $client_id, + 'overwrite' => 'true', + $key => $value, + ]); + + return $this->responseIsSuccess($data); + } + + public function saveInternetMode(string $client_name, bool $enabled): bool + { + $client_id = $this->getClientIdByName($client_name); + + if ($client_id <= 0) { + return false; + } + + $key = $this->getInternetModeSettingKey(); + + $params = [ + 'sa' => 'clientsettings_save', + 't_clientid' => $client_id, + 'overwrite' => 'true', + $key => $enabled ? '1' : '0', + ]; + + $data = $this->apiAction('settings', $params); + + return $this->responseIsSuccess($data); + } + + /** + * Get client internet authentication key. + * + * @param string $client_name Client name + * + * @return string + */ + public function getClientAuthKey(string $client_name): string + { + $settings = $this->getClientSettings($client_name); + + $raw = $settings['internet_authkey'] ?? $settings['internetAuthkey'] ?? ''; + + if (is_array($raw) && isset($raw['value'])) { + return (string) $raw['value']; + } + + if (is_array($raw)) { + return ''; + } + + return (string) $raw; + } + + /** + * Add a client to UrBackup server. + * + * @param string $client_name Client name + * + * @return bool + */ + public function addClient(string $client_name): bool + { + $data = $this->apiAction('add_client', [ + 'clientname' => $client_name, + ]); + + return $this->responseIsSuccess($data); + } + + /** + * Remove a client from UrBackup server. + * + * @param string $client_name Client name + * + * @return bool + */ + public function removeClient(string $client_name): bool + { + $client_id = $this->getClientIdByName($client_name); + + $payload = [ + 'clientname' => $client_name, + ]; + + if ($client_id > 0) { + $payload['clientid'] = $client_id; + } + + $data = $this->apiAction('remove_client', $payload); + + return $this->responseIsSuccess($data); + } + + /** + * Start incremental file backup. + * + * @param string $client_name Client name + * + * @return bool + */ + public function startIncrementalFileBackup(string $client_name): bool + { + return $this->startBackup($client_name, 'incr_file'); + } + + /** + * Start full file backup. + * + * @param string $client_name Client name + * + * @return bool + */ + public function startFullFileBackup(string $client_name): bool + { + return $this->startBackup($client_name, 'full_file'); + } + + /** + * Start incremental image backup. + * + * @param string $client_name Client name + * + * @return bool + */ + public function startIncrementalImageBackup(string $client_name): bool + { + return $this->startBackup($client_name, 'incr_image'); + } + + /** + * Start full image backup. + * + * @param string $client_name Client name + * + * @return bool + */ + public function startFullImageBackup(string $client_name): bool + { + return $this->startBackup($client_name, 'full_image'); + } + + /** + * Get recent backups. + * + * @param string $client_name Client name + * @param int $limit Limit + * + * @return array> + */ + public function getRecentBackups(string $client_name, int $limit = 40): array + { + $client_id = $this->getClientIdByName($client_name); + + if ($client_id <= 0) { + return []; + } + + $data = $this->apiAction('backups', [ + 'sa' => 'backups', + 'clientid' => $client_id, + ]); + + $rows = []; + + foreach (($data['backups'] ?? []) as $backup) { + if (is_array($backup)) { + $backup['backup_type'] = __('File backup', 'urbackup'); + $rows[] = $backup; + } + } + + foreach (($data['backup_images'] ?? []) as $backup) { + if (is_array($backup)) { + $backup['backup_type'] = __('Image backup', 'urbackup'); + $rows[] = $backup; + } + } + + usort($rows, static function (array $a, array $b): int { + $timeA = (int) ($a['time'] ?? $a['backuptime'] ?? $a['backup_time'] ?? $a['created'] ?? 0); + $timeB = (int) ($b['time'] ?? $b['backuptime'] ?? $b['backup_time'] ?? $b['created'] ?? 0); + return $timeB <=> $timeA; + }); + + return array_slice($rows, 0, $limit); + } + + /** + * Get client log rows. + * + * @param string $client_name Client name + * @param int $limit Limit + * + * @return array> + */ + public function getClientLogs(string $client_name, int $limit = 50): array + { + $client_id = $this->getClientIdByName($client_name); + + $data = $this->apiAction('livelog', [ + 'clientid' => $client_id, + 'lastid' => $this->lastlogid, + ]); + + $logs = []; + + foreach (($data['logdata'] ?? []) as $row) { + if (is_array($row)) { + $logs[] = $row; + } + } + + if (count($logs) > 0) { + $last_entry = end($logs); + $this->lastlogid = (int) ($last_entry['id'] ?? 0); + } + + return array_slice($logs, 0, $limit); + } + + /** + * Start backup command. + * + * @param string $client_name Client name + * @param string $type Backup type + * + * @return bool + */ + private function startBackup(string $client_name, string $type): bool + { + $client_id = $this->getClientIdByName($client_name); + + if ($client_id <= 0) { + return false; + } + + $data = $this->apiAction('start_backup', [ + 'start_client' => $client_id, + 'start_type' => $type, + ]); + + if (isset($data['result']) && is_array($data['result'])) { + foreach ($data['result'] as $result) { + if (isset($result['start_ok']) && $result['start_ok'] === true) { + return true; + } + } + } + + return $this->responseIsSuccess($data); + } + + /** + * Execute an authenticated API action. + * + * @param string $action Action name + * @param array $params Parameters + * + * @return array + */ + private function apiAction(string $action, array $params = []): array + { + if (!$this->logged_in) { + $this->login(); + } + + $params['ses'] = $this->session; + + return $this->request($action, $params, 'POST', true); + } + + /** + * Execute HTTP request. + * + * @param string $action API action + * @param array $params Parameters + * @param string $method HTTP method (GET/POST) + * @param bool $require_success Require successful HTTP + * + * @return array + */ + private function request(string $action, array $params, string $method = 'POST', bool $require_success = true): array + { + if (!function_exists('curl_init')) { + throw new RuntimeException(__('PHP cURL extension is required for UrBackup API.', 'urbackup')); + } + + $ch = curl_init(); + + if ($ch === false) { + throw new RuntimeException(__('Unable to initialize cURL.', 'urbackup')); + } + + $url = $this->base_url . '?a=' . urlencode($action); + + if ($method === 'GET') { + $url .= '&' . http_build_query($params); + } + + $options = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => $this->timeout, + CURLOPT_SSL_VERIFYPEER => !$this->ignore_ssl, + CURLOPT_SSL_VERIFYHOST => $this->ignore_ssl ? 0 : 2, + CURLOPT_HTTPHEADER => [ + 'Accept: application/json', + 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8', + ], + ]; + + if ($method === 'POST') { + $options[CURLOPT_POST] = true; + $options[CURLOPT_POSTFIELDS] = http_build_query($params); + } + + $options[CURLOPT_URL] = $url; + + curl_setopt_array($ch, $options); + + $raw = curl_exec($ch); + + if ($raw === false) { + $error = curl_error($ch); + curl_close($ch); + + throw new RuntimeException( + sprintf(__('UrBackup API request failed: %s', 'urbackup'), $error) + ); + } + + $http_code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + + curl_close($ch); + + if ($require_success && ($http_code < 200 || $http_code >= 300)) { + throw new RuntimeException( + sprintf(__('UrBackup API returned HTTP status %d.', 'urbackup'), $http_code) + ); + } + + if ($raw === '' || $raw === 'null') { + return []; + } + + $decoded = json_decode($raw, true); + + if (!is_array($decoded)) { + if (str_starts_with(trim($raw), '<')) { + throw new RuntimeException( + sprintf(__('UrBackup API returned non-JSON response (HTML). Check server URL and authentication.', 'urbackup')) + ); + } + + return [ + 'raw' => $raw, + 'success' => $http_code >= 200 && $http_code < 300, + ]; + } + + return $decoded; + } + + /** + * Evaluate generic API success response. + * + * @param array $data Response + * + * @return bool + */ + private function responseIsSuccess(array $data): bool + { + if (isset($data['success']) && $data['success'] === true) { + return true; + } + + if (isset($data['ok']) && $data['ok'] === true) { + return true; + } + + if (isset($data['saved_ok']) && $data['saved_ok'] === true) { + return true; + } + + if (isset($data['result']) && $data['result'] === 'ok') { + return true; + } + + if (isset($data['start_ok']) && $data['start_ok'] === true) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/templates/asset/asset_tab.html.twig b/templates/asset/asset_tab.html.twig new file mode 100644 index 0000000..c3bc354 --- /dev/null +++ b/templates/asset/asset_tab.html.twig @@ -0,0 +1,77 @@ +
+ {% if not linked %} +
+ {{ __('No UrBackup server linked.', 'urbackup') }} +
+ + + + + + + + + + + + + {% if is_sub_location %} + + + + {% endif %} + {% if servers|length == 0 %} + + + + {% elseif can_connect %} + + + + + {% else %} + + + + {% endif %} +
{{ __('UrBackup server selection', 'urbackup') }}
{{ __('Asset location ID', 'urbackup') }}{{ asset_location_id }}
{{ __('Root location ID', 'urbackup') }}{{ root_location_id }}
+ {{ __('The asset is in a sub-location. The plugin will use the server assigned to the root location.', 'urbackup') }} +
+
+ {{ __('No UrBackup server available for the root location of this asset.', 'urbackup') }} +
+
{{ __('Available servers for root location', 'urbackup') }} + + + + + + + +
+ {{ __('A server is available, but you do not have permission to link this asset.', 'urbackup') }} +
+ {% else %} + + + + + + + + + + + + + + + + +
{{ __('UrBackup status', 'urbackup') }}
{{ __('Linked server', 'urbackup') }}{{ server_link }}{{ __('IP address', 'urbackup') }}{{ server_ip }}
{{ __('UrBackup server version', 'urbackup') }}{{ server_version }}{{ __('Client name', 'urbackup') }}{{ client_name }}
+ {% endif %} +
diff --git a/templates/config/config.html.twig b/templates/config/config.html.twig new file mode 100644 index 0000000..a3011e8 --- /dev/null +++ b/templates/config/config.html.twig @@ -0,0 +1,39 @@ +
+
+ + + + + + + + + + {% for itemtype, label in configurable_types %} + + + {% if itemtype == 'Computer' %} + + {% else %} + + {% endif %} + + + {% endfor %} + + + +
{{ __('UrBackup configuration', 'urbackup') }}
{{ __('Asset type', 'urbackup') }}{{ __('Enabled', 'urbackup') }}{{ __('Default', 'urbackup') }}
{{ label }}{{ __('Always enabled', 'urbackup') }} + + + {% if itemtype == 'Computer' %} + {{ __('Yes', 'urbackup') }} + {% endif %} +
+ +
+
+ +
diff --git a/templates/profile.html.twig b/templates/profile.html.twig new file mode 100644 index 0000000..18a77d8 --- /dev/null +++ b/templates/profile.html.twig @@ -0,0 +1,34 @@ +{# + # UrBackup plugin for GLPI + # ------------------------------------------------------------------------- + # LICENSE + # + # This file is part of UrBackup. + # + # UrBackup is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2 of the License, or + # (at your option) any later version. + # + # UrBackup is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + # GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with UrBackup. If not, see . + # ------------------------------------------------------------------------- + #} + +
+
+ {% do profile.displayRightsChoiceMatrix(rights, {'title': title}) %} + +
+ + {{ call('Html::submit', [_x('button', 'Save'), {'name': 'update'}])|raw }} +
+ + +
+
\ No newline at end of file diff --git a/templates/server/server_form.html.twig b/templates/server/server_form.html.twig new file mode 100644 index 0000000..14e1027 --- /dev/null +++ b/templates/server/server_form.html.twig @@ -0,0 +1,118 @@ +{% extends 'layout.html.twig' %} + +{% block content %} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if server.id > 0 %} + + + + + + + + + {% endif %} +
{{ __('UrBackup server', 'urbackup') }}
{{ __('Name') }} + + {{ __('Active') }} + +
{{ __('Entity') }} + {{ render_entity_dropdown('entities_id', server.entities_id ?? 0) }} + {{ __('Recursive') }} + +
{{ __('Location') }} + {{ render_location_dropdown('locations_id', server.locations_id ?? 0) }} +
{{ __('Associate the server with the main/root location. Assets in sub-locations will use this root location server.', 'urbackup') }} +
{{ __('Protocol', 'urbackup') }} + +
{{ __('IP address', 'urbackup') }} + + {{ __('Network port', 'urbackup') }} + +
{{ __('UrBackup server version', 'urbackup') }} + + {{ __('Ignore SSL verification', 'urbackup') }} + +
{{ __('API username', 'urbackup') }} + + {{ __('API password', 'urbackup') }} + +
{{ __('Comments') }} + +
{{ __('UrBackup web interface', 'urbackup') }} + {% if server.url %} + + {{ __('Open UrBackup interface', 'urbackup') }} + + {% else %} + {{ __('No URL available', 'urbackup') }} + {% endif %} +
{{ __('API test', 'urbackup') }} + + +
+ + + +
+
+{% endblock %} diff --git a/templates/server/server_list.html.twig b/templates/server/server_list.html.twig new file mode 100644 index 0000000..f00e847 --- /dev/null +++ b/templates/server/server_list.html.twig @@ -0,0 +1,51 @@ +{% extends 'layout.html.twig' %} + +{% block content %} +
+ + + + + + + + + + + + + {% for server in servers %} + + + + + + + + + {% else %} + + + + {% endfor %} +
{{ __('UrBackup servers', 'urbackup') }}
{{ __('Name') }}{{ __('IP address', 'urbackup') }}{{ __('Port') }}{{ __('Version') }}{{ __('Status') }}{{ __('Actions') }}
{{ server.name }}{{ server.ip_address }}{{ server.port }}{{ server.server_version }} + {% if server.last_api_status %} + {{ __('OK', 'urbackup') }} + {% else %} + {{ __('Failed', 'urbackup') }} + {% endif %} + + + {{ __('View') }} + + +
{{ __('No records found', 'urbackup') }}
+ {% if can_create %} + + {{ __('Add') }} + + {% endif %} +
+{% endblock %}