Compare commits
1319 Commits
17.0
...
cetmix_tow
| Author | SHA1 | Date | |
|---|---|---|---|
| 6855e3711a | |||
| 26f2040905 | |||
| a52b141017 | |||
| 5d988b1cb8 | |||
| 4fc18d865b | |||
| 262a6e4b84 | |||
| e450738fd7 | |||
| 83ec459ca5 | |||
| 4de853d788 | |||
| ad6cbac1f8 | |||
| 2fcd451339 | |||
| 762547c1f5 | |||
| 25cc185aee | |||
| 0fc6a1d6f3 | |||
| 76991aecae | |||
| f7c03a7122 | |||
| dc0fa2dff7 | |||
| 99043f1c52 | |||
| 73a89f15e6 | |||
| b4a3b13ee0 | |||
| 3d30491875 | |||
| dda64246c5 | |||
| a0d1d19687 | |||
| 66c81b2a91 | |||
| c945b52671 | |||
| 146d71319e | |||
| d6d2136df6 | |||
| 9c9cc898a4 | |||
| be07a3b18d | |||
| 9f312687b1 | |||
| eadb83779e | |||
| e244e8279b | |||
| 47fe5ea7a5 | |||
| 05724afff0 | |||
| 5578fb365a | |||
| 30a3b0dc4e | |||
| 51c5cb3bdb | |||
| 0849ae6161 | |||
| 81ee76ce21 | |||
| dc76af271e | |||
| 5f868f7610 | |||
| cf31963487 | |||
| f1b923ae7f | |||
| 0484142dd5 | |||
| 806c7ce8b8 | |||
| 424742714d | |||
| 87b5247726 | |||
| e225e7b2a2 | |||
| 1877e3c1ae | |||
| 3ea304cb45 | |||
| d49b02938a | |||
| 8db12c649f | |||
| 9c16569b69 | |||
| c2813bc9b3 | |||
| 5111caa738 | |||
| 2f302772e3 | |||
| 0deb721477 | |||
| 178f8e137e | |||
| 0769cb0756 | |||
| 95485e2558 | |||
| ab144b1350 | |||
| 4c6fd5e470 | |||
| 05e045267a | |||
| f5a9261856 | |||
| 764642fbf1 | |||
| 9170934142 | |||
| a0877d3ba4 | |||
| d236c96001 | |||
| 5f76fc4ad5 | |||
| db4e11225b | |||
| 28987afc7d | |||
| eff6288a42 | |||
| af344b5014 | |||
| e97a22516c | |||
| d99c2f23a9 | |||
| 49b0220cc1 | |||
| 8f87c713f3 | |||
| eb2ad30e64 | |||
| 1ebf77f1aa | |||
| 32e517b5ec | |||
| fe243328a0 | |||
| 070314632d | |||
| b9c4a621dc | |||
| 5f9fb1597b | |||
| 62edb14057 | |||
| 2b1c121be9 | |||
| 51efac175a | |||
| 7e737b5877 | |||
| 2beb85437a | |||
| cfdd00e264 | |||
| 034ea5c0bd | |||
| 8621fac655 | |||
| 8a5b68926c | |||
| 22885f7fdd | |||
| e3e51b8367 | |||
| 019224ba4c | |||
| 833346a1a8 | |||
| c501af7d45 | |||
| aa1b8801ce | |||
| b48081c8e2 | |||
| a366d1b52c | |||
| 5cb28ea01a | |||
| 30f1f2df49 | |||
| c416aabc44 | |||
| 17d150a45f | |||
| 6211de488b | |||
| 5fd192356f | |||
| 929448f1ca | |||
| 8733b3cb61 | |||
| c5fa399627 | |||
| 4ce9f94318 | |||
| 281c0167b1 | |||
| 96d4ad7ef7 | |||
| 6ce7c48b2d | |||
| c3bdb2c14d | |||
| 898b423feb | |||
| 7264942e8d | |||
| 3f23cfecf3 | |||
| 23e386b526 | |||
| f0193a9307 | |||
| bbddf942a2 | |||
| f7d3a429a5 | |||
| 89bba86349 | |||
| 0a2076df37 | |||
| 7da0bc5c93 | |||
| 2c7bea7e69 | |||
| cc4bde613b | |||
| a3e7e80ffb | |||
| 3d2174b4e8 | |||
| 436bc160e3 | |||
| 693821eb53 | |||
| 495bb536f1 | |||
| a81ee9d711 | |||
| 13f88ed1ed | |||
| 9c92bd8a2d | |||
| 9815bfd407 | |||
| 42256b6283 | |||
| 4d4b874ee0 | |||
| e741fd3d1c | |||
| 23db6fae45 | |||
| 0cac17c395 | |||
| 9bea4833ca | |||
| d70d24cb7a | |||
| ab129128b3 | |||
| 8c292c2217 | |||
| 0b504afdca | |||
| fa76207199 | |||
| a6d3222ffc | |||
| e7b8c1fc11 | |||
| 226ecfa11e | |||
| 92a34a2292 | |||
| 19b6b2caca | |||
| 448f814aae | |||
| 2537f4e58c | |||
| d0059616aa | |||
| 8c8199abbd | |||
| 64825c8e84 | |||
| 62cd370099 | |||
| 9b528e38fc | |||
| c45450ed87 | |||
| cc53a55c96 | |||
| 9573216bfd | |||
| 2839e110fe | |||
| ba9ce2ad88 | |||
| eb4d7a5477 | |||
| 42d21b989a | |||
| 23cf3ad81b | |||
| cce324dbfb | |||
| 9e1dcd02dd | |||
| bfbe68ff88 | |||
| e07234573c | |||
| 5b2f53b33a | |||
| 14e7468ca7 | |||
| 7df06465a8 | |||
| cb2eb054eb | |||
| 3ef2cc50fe | |||
| d12d454c70 | |||
| 9a17bcd25e | |||
| 73ebe069f6 | |||
| 2755e373fd | |||
| 9e43910cc8 | |||
| 1646ace09b | |||
| c6c6570800 | |||
| 21576ec28f | |||
| a5b60a5d3b | |||
| abcb71d469 | |||
| 4fdf6333f2 | |||
| c18aba668b | |||
| 0a8333e1e2 | |||
| 3d19db5049 | |||
| 62e7767925 | |||
| 070c89e75e | |||
| cf0d897dfa | |||
| 9fc1c6bd65 | |||
| 342e616963 | |||
| a4c6f5c561 | |||
| 811d32c5be | |||
| 135074c040 | |||
| af55099d83 | |||
| 2d9f32fc2f | |||
| d361711043 | |||
| 426c0e0792 | |||
| ae451e5911 | |||
| 31bcb48704 | |||
| 25703173fb | |||
| eab2080115 | |||
| 5f99227e6c | |||
| 4a547632ac | |||
| 8cd9bae8ea | |||
| ddadefa9a6 | |||
| 7276688114 | |||
| 66450d4d02 | |||
| 2d0bda98b1 | |||
| 89943c26eb | |||
| 0ac25c7405 | |||
| b54c955847 | |||
| 857ec4fceb | |||
| 83ff1a0ec5 | |||
| ef85be3808 | |||
| 25b80d98ce | |||
| 1871e1ffe9 | |||
| 4440daa0a4 | |||
| 6e018447b2 | |||
| 5c4949bf5b | |||
| 90cb176847 | |||
| bdf8278b7f | |||
| da1f2fd426 | |||
| 4b1cbbc86b | |||
| 0957e4d55b | |||
| 6509c2136f | |||
| 52877f9b2c | |||
| 6176d27861 | |||
| 37a160148d | |||
| ee1501034b | |||
| 92e62ae21b | |||
| 7a5d6aa254 | |||
| 42a4abb176 | |||
| 440324c078 | |||
| 7e9e92a179 | |||
| ad62d49f3d | |||
| a562808d99 | |||
| 7f6a00a8f7 | |||
| f04db1b076 | |||
| 236b4a6227 | |||
| 93be36fb52 | |||
| 412f99f6a1 | |||
| a00f32e12f | |||
| 155436e103 | |||
| c43c7035a7 | |||
| a68656e1d5 | |||
| 65a7419e30 | |||
| d334b0c87b | |||
| 4826cd422e | |||
| 0f8a0bddba | |||
| 155faf8f8b | |||
| b8e8b0d989 | |||
| 8f8eec47a9 | |||
| c0767d1041 | |||
| 4bb470d291 | |||
| 7f4f78e750 | |||
| 6640a61437 | |||
| 0e7e46d0bb | |||
| 367f45a9b3 | |||
| 1d4e56cbd9 | |||
| 4e07525d10 | |||
| f5d6ad46c4 | |||
| cc8804103b | |||
| f7f24085cd | |||
| 097cf223ec | |||
| 8c3381f41b | |||
| 05a553a8fb | |||
| e71e1e04f4 | |||
| cc614c3ecf | |||
| 49feadbeb9 | |||
| 66d1bf5e47 | |||
| 7d39b690ec | |||
| 48c79615fe | |||
| 55dac4ca23 | |||
| a861002926 | |||
| 462f134278 | |||
| 1a42ca3cb1 | |||
| f5574ed238 | |||
| 3f00653bb2 | |||
| 505f69fb27 | |||
| 85ff0c5741 | |||
| b91b95bccd | |||
| 984be00635 | |||
| 01fd85aec0 | |||
| 6aa60f1bc3 | |||
| 55bad508da | |||
| 99ec962ca3 | |||
| d4b094937a | |||
| 19e36c1a35 | |||
| ce15caaa45 | |||
| 315e1bd1e7 | |||
| ff8d274a38 | |||
| ab2c9cddae | |||
| 16dc380c53 | |||
| 4b5170e8b7 | |||
| c7912ec5a6 | |||
| 090f0099c0 | |||
| ed7a1d93ba | |||
| cbf850a987 | |||
| 877cbcdd6b | |||
| 44e10c9212 | |||
| a2742e95ec | |||
| 9105818e3a | |||
| f06a374569 | |||
| 1c38931430 | |||
| fa92ab9ba8 | |||
| 4b56acc480 | |||
| f3cb0587f5 | |||
| 3e756c774f | |||
| e8fe926dbb | |||
| b2a168bd06 | |||
| 4628821451 | |||
| ab108c0437 | |||
| 5d9fe7b1f3 | |||
| 1b3bf2d301 | |||
| 017d7883d3 | |||
| 00439eac6e | |||
| d5b31de87e | |||
| f9a6b56c81 | |||
| 123239031f | |||
| 7f65dc9a92 | |||
| 25c345f665 | |||
| 2c2058e07b | |||
| f628de0c15 | |||
| 998d97da45 | |||
| 7a6f12082b | |||
|
|
9bb80002c8 | ||
| 2cf3b5185d | |||
| 9214b650ae | |||
| e6027c710b | |||
| 7a40f423d4 | |||
| 9966aa4ffa | |||
| 7c90e7bee2 | |||
| be3a699471 | |||
| a68f5ed5da | |||
| 3a4d546c10 | |||
| 386893e751 | |||
| e8cd94cc98 | |||
| bbf383a59f | |||
| eb41cf2557 | |||
| 7d71cd87fe | |||
| 886affc442 | |||
| 236c25028c | |||
| ca0996089d | |||
| 326bbe4eb1 | |||
| bd2de68a13 | |||
| 241485ada6 | |||
| 93925e7edb | |||
| 8be03b6213 | |||
| f8be9708f2 | |||
| 43f1d3a460 | |||
| 95ab9b7b07 | |||
| c4dec5820f | |||
| cf8020a374 | |||
| 8b2b00309b | |||
| 43c386dd0f | |||
| 94e68bf101 | |||
| c6fcbd4e88 | |||
| 95ec10c4cd | |||
| 8cde5f5b85 | |||
| 92daedbcfe | |||
| 59af83f001 | |||
| 68ca5feaec | |||
| d379066f8d | |||
| 50f45a25f6 | |||
| 4f1b44f859 | |||
| 63cb075ebe | |||
| 3bc89980fb | |||
| a30f10f463 | |||
| a3f387f59d | |||
| 0496b94742 | |||
| eeea0bde1d | |||
| 19a63eac0c | |||
| ff42b73982 | |||
| ac9207b0c6 | |||
| 7dadc5938a | |||
| 88fb907a24 | |||
| a349d97184 | |||
| 2eee80609f | |||
| fcb747f2c9 | |||
| 826194b88f | |||
| 303d179c1a | |||
| b309b6f244 | |||
| ee0ef6b69d | |||
| 121fcd2639 | |||
| 5cecb3364e | |||
| 1e2909808a | |||
| b8658d0fed | |||
| ea586f0019 | |||
| 4219a55576 | |||
| 2d6197c181 | |||
| 049d8dc461 | |||
| 8f7afa25fc | |||
| 7c0c8cf62a | |||
| e0c9e3817f | |||
| 95894e0965 | |||
| 0fd426db38 | |||
| 7f38305cda | |||
| 9eb03938ba | |||
| 38b16543da | |||
| 3a6e036b1d | |||
| c28baccca8 | |||
| 2352fdfaab | |||
| fd93d95ef0 | |||
| 1ad508d029 | |||
| 80e2953742 | |||
| 8f2214eb37 | |||
| 105434392b | |||
| d77c0ec323 | |||
| ebc9c2da81 | |||
| b706079a96 | |||
| 1a3fb88ff0 | |||
| fe73ec564d | |||
| 38becb2347 | |||
| 9cc237dfcd | |||
| 59a0a18068 | |||
| 3cdcbd70c3 | |||
| 36f48cb3ef | |||
| 6e40ceba46 | |||
| 9ed9312f52 | |||
| 3bf88a3db2 | |||
| cd835868a4 | |||
| 3adbefa88a | |||
| 6192b3aff5 | |||
| b504a42afc | |||
| 60532fda2e | |||
| 4604622231 | |||
| f4e43a2f84 | |||
| c5c3887d97 | |||
| 8ce3697a6e | |||
| bbe37b34d9 | |||
| ae8c208c59 | |||
| 3811a9d7cd | |||
| 3eed4a9224 | |||
| 68b713a69a | |||
| af1be38c68 | |||
| 3780eaf1f2 | |||
| 1f23472908 | |||
| 5e71e0a98a | |||
| 5e665079f3 | |||
| b67d86ef4b | |||
| cf83815785 | |||
| 255a06a789 | |||
| 3153e06601 | |||
| 7e4c9dcd99 | |||
| 8e92de0c4e | |||
| eb58ac434c | |||
| 5aed68117b | |||
| b6c3a855a4 | |||
| cd4b7cd2e6 | |||
| cb1e060ebb | |||
| ccc9ef0644 | |||
| 03b1d2bd59 | |||
| 565ef8f0cc | |||
| ae52efe220 | |||
| 09576d37b4 | |||
| 3b556e385d | |||
| d24677fc8e | |||
| bc9c9bf125 | |||
| 953db1c18e | |||
| bb6408e1bb | |||
| f5ee80a075 | |||
| e2a1ef97cf | |||
| f7aa151993 | |||
| c8461e648d | |||
| 78f57a52e8 | |||
| 645bbb9e1f | |||
| 1c5b8f78e8 | |||
| b06be61868 | |||
| 22635fd1db | |||
| 502f3c276f | |||
| a29e7d3701 | |||
| 90a6de998c | |||
| 309834e218 | |||
| 36548b07b3 | |||
| a647db524a | |||
| 96392d7cab | |||
| 3c73cf7286 | |||
| 5128d56863 | |||
| ec366ca548 | |||
| eaaf033cd5 | |||
| 899eefb2b1 | |||
| 35c152c073 | |||
| 4db61b7221 | |||
| 56faad93d6 | |||
| b02021eeab | |||
| 0f8d4c52be | |||
| f6b23b2697 | |||
| 9c162c8c40 | |||
| 9ee9d2dc06 | |||
| f88873226d | |||
| e6ef101e11 | |||
| ace82b83f0 | |||
| c3b5ebd7bc | |||
| f52001bffa | |||
| 8289f0ec93 | |||
| 4c85b5da81 | |||
| 7bddb74ba7 | |||
| b088c69c2b | |||
| ffc66cfaa1 | |||
| 50705d8a20 | |||
| eb85e1f034 | |||
| c104fc8c43 | |||
| 12aaeebb69 | |||
| b8accd5199 | |||
| 36e718ae36 | |||
| fc67cb263f | |||
| d6bec900fe | |||
| db967ff6e3 | |||
| 9d6d07b2e1 | |||
| 1444a3064e | |||
| 0a2e334aee | |||
| 15d21d3ab5 | |||
| a4f565087c | |||
| 6a11883c7c | |||
| 1202892973 | |||
| d3e1cc28bc | |||
| 151246c614 | |||
| 5049ba6907 | |||
| f8b593bec4 | |||
| 682e48bb2b | |||
| c4ddcb8e92 | |||
| d992b45cdd | |||
| f89637d6e8 | |||
| a493701b6e | |||
| 77b6a4b2fc | |||
| 9d595b2565 | |||
| 812b64b18d | |||
| a6d4d64192 | |||
| 0f52520ee3 | |||
| 8a996f5083 | |||
| b4e0d93c28 | |||
| f54217e713 | |||
| ebb1399951 | |||
| f9d33713ab | |||
| 4af3a1e647 | |||
| af44da007b | |||
| 1d5a2ceed0 | |||
| 4f9d558cf4 | |||
| 8bdd6064ab | |||
| 76b43e0b65 | |||
| 3b80e2101c | |||
| 42653be822 | |||
| 717ce4756a | |||
| 5ac0c18619 | |||
| 79dd86e13f | |||
| 05b6f98a82 | |||
| 098ce63b26 | |||
| 254f6312b4 | |||
| 5dc4447ff3 | |||
| d77978d0c9 | |||
| c4df067894 | |||
| c210efa9a2 | |||
| 3dbf0baeb2 | |||
| 5903af189c | |||
| 507eab7847 | |||
| 0f5b16febc | |||
| 1b5342da13 | |||
| 9d2776f6ce | |||
| 716a6d735c | |||
| 5362b526e7 | |||
| 0586c40667 | |||
| 9e48c4e0dc | |||
| 569a853b3a | |||
| 74a3f434a4 | |||
| a6af9583df | |||
| 326900b3a7 | |||
| c4dfb0a886 | |||
| a5efdebe15 | |||
| cb84ce69be | |||
| 7e5354d5ad | |||
| 521d765b35 | |||
| 7fbaaf189b | |||
| 6a9182c820 | |||
| 90f7afa720 | |||
| 90c893ae66 | |||
| 8d55a7ee70 | |||
| f6e32ece58 | |||
| 162ad41852 | |||
| 8b89083a51 | |||
| 68c5b015e0 | |||
| 61e360ae9e | |||
| 50663c7700 | |||
| 4da1291a50 | |||
| 94df75a9bf | |||
| 5ea3184c5e | |||
| 099bb0dfd5 | |||
| e318c89788 | |||
| 1174604b05 | |||
| 364f37aa5a | |||
| bbb71840c1 | |||
| c2923e01e6 | |||
| 4131ae0adb | |||
| f16e0abf03 | |||
| 07adc2628c | |||
| a682b818d3 | |||
| b2a8c2dcde | |||
| 2314a8f6c2 | |||
| f2033e0408 | |||
| 38d4a641f1 | |||
| 8ca3561c1a | |||
| bf7158a6e8 | |||
| 6a92d586a4 | |||
| 2a93942be1 | |||
| e81e6f4ff1 | |||
| 5e4cf9723b | |||
| a6fb93b231 | |||
| a3022f8332 | |||
| af0b27d762 | |||
| d2a8d537f6 | |||
| 52d65dabba | |||
| cbd499b88f | |||
| d59aa0179a | |||
| bf61f4eb4f | |||
| 97352ac44d | |||
| eb0687da36 | |||
| ceea23aaaf | |||
| 802a5a8a1c | |||
| 2d24db2b08 | |||
| bfb1edac54 | |||
| b12ac35b65 | |||
| 8b881eba5b | |||
| b2317325e2 | |||
| a8e845cb8b | |||
| 14bd70a67c | |||
| 82cfb22c03 | |||
| 50cd192fd8 | |||
| 62a0a9b1bd | |||
| 88b9f087a1 | |||
| 55b5ad56c8 | |||
| 00d5145aa0 | |||
| 6161522a04 | |||
| abaabc4cc0 | |||
| 2686884335 | |||
| f41c8d4c6f | |||
| a9496bdbd2 | |||
| 5e634580c2 | |||
| 37b4590851 | |||
| 81e565fdec | |||
| b912c85da7 | |||
| 7ef63fa72d | |||
| 627c3e9ffa | |||
| 876b3f4a7d | |||
| f323562bbd | |||
| f98711ec66 | |||
| 8db97cf776 | |||
| f38452a52a | |||
| 03d392803f | |||
| 8b40cf2659 | |||
| 86cf1847f4 | |||
| 45d7ed8b73 | |||
| 0b7b196159 | |||
| 33ad700d61 | |||
| 7f8552981b | |||
| 5cc9d2512b | |||
| 6ab789e36d | |||
| f8109af6ce | |||
| 1bfc6ac3af | |||
| 548817116a | |||
| b2f712e6b6 | |||
| 93739ddaff | |||
| 1373305638 | |||
| 70a54d0787 | |||
| 40a164b1b0 | |||
| bc966d56b0 | |||
| 61e7953827 | |||
| 192e1a9dbc | |||
| 2fe3c98c53 | |||
| 5f3cca665d | |||
| 88d5b049ff | |||
| 4614cb31fb | |||
| 44d76bc4a1 | |||
| 353350476d | |||
| 76d63a2fe8 | |||
| cce75a5895 | |||
| 2409adec5e | |||
| f75a5247b5 | |||
| 8cfa1310d1 | |||
| 086b96cc39 | |||
| 908aa7e1da | |||
| b6218e6b07 | |||
| 3d868855ca | |||
| 7e7213f67b | |||
| f5afc7d31c | |||
| 72737afb61 | |||
| b39e81512e | |||
| 62fad98a12 | |||
| 98c4aa7358 | |||
| 1a3ee1d4de | |||
| be2facb1f1 | |||
| 97b4731ca6 | |||
| 83326bcaed | |||
| 09eda63698 | |||
| d0aaff2135 | |||
| 7bd0bd0989 | |||
| 94bd98ab65 | |||
| 25fa4c853b | |||
| 4b4581ff2e | |||
| e1ea01e59e | |||
| da4d5c2494 | |||
| 3713992eb3 | |||
| e1c8337464 | |||
| 6c81ab18c8 | |||
| f0085588b9 | |||
| 8fdb330a6c | |||
| fb7a94f8ae | |||
| e71e3822e0 | |||
| e289fb9145 | |||
| bf2a8c4010 | |||
| 5cc34775f3 | |||
| 7f75afbdba | |||
| 1390ec0c15 | |||
| 4e84600e7e | |||
| c0ea49872c | |||
| 0685e4cabe | |||
| 875a590978 | |||
| 98d8aeb011 | |||
| 2f9905210a | |||
| 41d56a304a | |||
| 55da1da7f3 | |||
| a65d315cda | |||
| 16e6c57a3f | |||
| 638a2df7c6 | |||
| 9b53282444 | |||
| 89d02b353b | |||
| c968cca4de | |||
| e163b0d99b | |||
| 4426a5abb6 | |||
| 8c8e527249 | |||
| f50c1865d5 | |||
| f5257dc7ab | |||
| 14315e0035 | |||
| e2fe62b379 | |||
| 1f9bea1264 | |||
| 08f1a4d3cb | |||
| be8a0ac9c3 | |||
| a383f11d2a | |||
| 91618edeec | |||
| b64f6da230 | |||
| ba4b26b175 | |||
| 525c919b3a | |||
| d7f6de08e9 | |||
| 0d20ab7aa8 | |||
| 231d59da7f | |||
| 4fef277751 | |||
| 7032e64dcb | |||
| 15481c11c0 | |||
| 5930f771e5 | |||
| 2290c90089 | |||
| eb28ba0f78 | |||
| 7df203efe8 | |||
| 766b1dd779 | |||
| e992a8ea3f | |||
| e3863de129 | |||
| bfed6bdcf4 | |||
| f4834218ce | |||
| e3e001ac5b | |||
| afc1985114 | |||
| f0c0fa15ac | |||
| 3bb17aa74a | |||
| 3887d3afd3 | |||
| ccf1ac8e77 | |||
| df84150f14 | |||
| fd4f3c89cf | |||
| 46fa14692c | |||
| 2d26b672bf | |||
| f908dd5acd | |||
| f50135b5c6 | |||
| 2be8fc95b7 | |||
| 736d4b40e0 | |||
| 7fa0a4c79a | |||
| 8881aeb2ca | |||
| 8fa1c29c59 | |||
| 318ef00817 | |||
| 3fcdf69f56 | |||
| e2785fff55 | |||
| e5adc46d44 | |||
| 6052fcbe7e | |||
| 268e7e9b23 | |||
| 53f93fe01b | |||
| 202cb09e2b | |||
| c29df047d2 | |||
| 8dc8a3d39b | |||
| 34a4c17e9b | |||
| ccb61bd1d9 | |||
| be89574d27 | |||
| e8a12c08d8 | |||
| 16a5812db0 | |||
| 366eeab65d | |||
| ce185f92e3 | |||
| 276dccf5fd | |||
| 4fc6c80352 | |||
| 016afe19f8 | |||
| 7295ad401c | |||
| 1eed0fbda0 | |||
| da449b9852 | |||
|
|
9a499aced7 | ||
| c3f2c4f522 | |||
| a356f71745 | |||
| 0a76509365 | |||
| 9083833444 | |||
| ed9dc11c4b | |||
| 1c2d483a46 | |||
| 1f073938d7 | |||
| e12133fff0 | |||
| 61d95f53d6 | |||
| ebd9d7c16f | |||
| 455a7192d3 | |||
| 4c8ec8ffa3 | |||
| 2beca515ca | |||
| e1c3544f09 | |||
| 242fc36fd0 | |||
| 4754df89b7 | |||
| e050040bae | |||
| 30deb10c27 | |||
| f2791efa4f | |||
| acc9748aa2 | |||
| b36fe277c0 | |||
| 0b07e80ecb | |||
| b448019f93 | |||
| a108daebd0 | |||
| 2c950b5a0a | |||
| 0ded373257 | |||
| 4ad6ffce26 | |||
| 265f8092e5 | |||
| 6735056551 | |||
| a70aae6404 | |||
| f7c2027d17 | |||
| 8f9c4f4ed9 | |||
| cd4fa9df4d | |||
| b776dea93e | |||
| 2d655e9822 | |||
| f1aef07115 | |||
| ceac54f2a8 | |||
| 8cd2d73dd1 | |||
| 4c8a85091e | |||
| 1d8e306a5b | |||
| ff76148774 | |||
| 263616cf66 | |||
| 5d8563cf5e | |||
| 3a8e7bb486 | |||
| dac924d260 | |||
| 2fec9bae57 | |||
| a3a0e7f1a9 | |||
| 57e4922aa4 | |||
| 5a86e951c9 | |||
| 07a2292b98 | |||
| 498ebf5b5e | |||
| 0c0a53fb80 | |||
| f13d08283f | |||
| 6776b72adf | |||
| fc0111f9af | |||
| 58ecb0871a | |||
| 5ab36dc181 | |||
| 9eb4e7eb59 | |||
| b73d178751 | |||
| aeb5997303 | |||
| b6be8f1f51 | |||
| 6e4ec0f188 | |||
| d84a9d95e3 | |||
| e85ead856e | |||
| d640f69b64 | |||
| 3a855c6905 | |||
| e5bc9f79bc | |||
| e4a9a645a1 | |||
| 7be7fc0d06 | |||
| 639c21ee64 | |||
| 83463f08fd | |||
| d62c41a4c0 | |||
| 5bc32a7b39 | |||
| b87f9f2f0f | |||
| ecb8f48fc7 | |||
| 453e861eb1 | |||
| 9d9ba90f65 | |||
| c0ec43dc15 | |||
| 8bf0f53a1f | |||
| b9935804d8 | |||
| e3cd173cae | |||
| 0b171536bd | |||
| 4df5459abb | |||
| 99c360c739 | |||
| 238f3a123f | |||
| 7635f192bc | |||
| 4be4c95126 | |||
| c7174e1aa0 | |||
| 038f113176 | |||
| e640b7dc00 | |||
| 69ee0e8a79 | |||
| 44f9bf1d9d | |||
| 528d049505 | |||
| d38fff270d | |||
| f47e6d77df | |||
| bcdb9f7100 | |||
| 79c3511ced | |||
| 77163b4017 | |||
| 52512ff961 | |||
| 8e37d624ff | |||
| 2b845fa110 | |||
| 09c730c852 | |||
| 9ea0b5ca1e | |||
| 3f7c12c4cb | |||
| 52212a4234 | |||
| aad83a6e45 | |||
| b15249a7cc | |||
| 971c0b6bb1 | |||
| c8259d3fae | |||
| c1c1591ffd | |||
| 1ceae80007 | |||
| 711c240058 | |||
| ed906caf54 | |||
| fa316c0948 | |||
| 64da73b835 | |||
| 51d27b362c | |||
| 11bf6134ec | |||
| e6fd3d1594 | |||
| 6c440b46d0 | |||
| 6f5049ff54 | |||
| 785da91996 | |||
| a59be21d5c | |||
| dcd8d31c0b | |||
| bbff9cd347 | |||
| c5433c7b44 | |||
| 52e3ae85ef | |||
| 6ee8d34a08 | |||
| fc845bf79a | |||
| aa3ea16a1b | |||
| 5d72ad53fb | |||
| d5a16b3baa | |||
| 4300c11cb1 | |||
| 611bf9a530 | |||
| f665a077cb | |||
| 62c2411818 | |||
| 1aeff8903c | |||
| 7a0fe4f363 | |||
| 6ebe68c8fc | |||
| 075a340056 | |||
| d764da736d | |||
| b9ee75209a | |||
| 389ffcbadb | |||
| 37ec61eab2 | |||
| a901150981 | |||
| d7e623d816 | |||
| 45e7dd2c6c | |||
| 25ca66a86b | |||
| a78cbd6583 | |||
| 09283bba61 | |||
| e738070b24 | |||
| 0c67e0d798 | |||
| 9e7f379923 | |||
| 93426fbae5 | |||
| f1b9101b13 | |||
| 33d3b02763 | |||
| 2a169afa62 | |||
| a2fc7b47c1 | |||
| c717460cea | |||
| a8f57858d8 | |||
| 3ab0e79386 | |||
| 236b78c33b | |||
| 18138fd178 | |||
| fa34dcb44e | |||
| f8a7e22755 | |||
| 5e7a05b949 | |||
| 676d1fd8c1 | |||
| 9a569b9af1 | |||
| bfda22756e | |||
| 237d692cd1 | |||
| 31b57b24f5 | |||
| ff8cc52e41 | |||
| f6d8bab742 | |||
| 1bd0a301dd | |||
| d7ac3f27e0 | |||
| adf18c04a9 | |||
| 150023c203 | |||
| 21bf17da38 | |||
| 54c3d037a0 | |||
| cc7c59f4fa | |||
| 491c5fa696 | |||
| cb1c08a52d | |||
| 7c414a48de | |||
| 0d03d8d956 | |||
| ee974eb8d1 | |||
| 5e17847c10 | |||
| 753ee2e4e3 | |||
| 2c73095fd6 | |||
| c31875dc95 | |||
| 39ac32de74 | |||
| 3a6f76212d | |||
| 89c5d6195a | |||
| a6c0f93af0 | |||
| f52cdfe12d | |||
| fe91357505 | |||
| e4c578f20a | |||
| d9734e17fb | |||
| 4cc3f94635 | |||
| 8ff4dd5b3c | |||
| 423b6acfde | |||
| ccf2ec0804 | |||
| 002f0bda6f | |||
| 753aee7fc5 | |||
| c96b8b5c64 | |||
| 821e088b8e | |||
| 3ea5965a99 | |||
| 5ce2821634 | |||
| 5d4b6ee4fc | |||
| 7399e63d70 | |||
| fc27945b4e | |||
| 2b98a8ca57 | |||
| d50103ca24 | |||
| 52a3aaec81 | |||
| 48d9f3af80 | |||
| 7575ca93ad | |||
| dcf7f0daa9 | |||
| ffe3f45168 | |||
| bd17bdabf9 | |||
| 118c993e31 | |||
| 33707afaaf | |||
| 5fc79cb304 | |||
| b595872bb3 | |||
| e50e55444c | |||
| 0f68d7e8f9 | |||
| f161ba7728 | |||
| fb2479c6fe | |||
| f8f75587c3 | |||
| 3cbf6b5144 | |||
| 48cf579779 | |||
| 5c5edd2106 | |||
| 4a4b2f7206 | |||
| e84cdd1a54 | |||
| 410eaab039 | |||
| 8b8dd4758f | |||
| 16dfa61d3d | |||
| cd0dcdb06b | |||
| 0bc76ebcfb | |||
| b86383444b | |||
| e788902689 | |||
| 2d0d90ba7e | |||
| c56cba7257 | |||
| 63623d0dc3 | |||
| 1a409eab82 | |||
| 135242d577 | |||
| aec96c5193 | |||
| a68eef6627 | |||
| 0ed1ee7bb0 | |||
| ccdde704c7 | |||
| f2803833a6 | |||
| 884df448f0 | |||
| a1cba28258 | |||
| eddb6db49e | |||
| 67267c20a3 | |||
| 547da816ad | |||
| ecf57118ba | |||
| 3681255837 | |||
| f97595108f | |||
| 7bf6c0aa4b | |||
| 9fb24cad21 | |||
| b036b9f480 | |||
| 2d677619f3 | |||
| 576e6edecc | |||
| 0819ee5593 | |||
| 7ca45462cf | |||
| 0e8399ae71 | |||
| cf42f5455d | |||
| 50536e5867 | |||
| e3c584c560 | |||
| c48e67db97 | |||
| fd8c6f50f2 | |||
| e09e6074c8 | |||
| c470e9603f | |||
| faf0882b3a | |||
| e96209ad14 | |||
| 1912047142 | |||
| 8011416ceb | |||
| 2d3f527e24 | |||
| aed221fa34 | |||
| 425d46b0ef | |||
| b3d43ad682 | |||
| 65af6a8437 | |||
| ac07f2e7df | |||
| 4d1c88b43b | |||
| 465720ccdd | |||
| 21fe8ed322 | |||
| 01d440de88 | |||
| aba0ce3a6d | |||
| 906217822f | |||
| 9e81d94e5d | |||
| 092f3b1026 | |||
| 6739cac57b | |||
| 2d69ab84ce | |||
| af3a8d4dbc | |||
| 0792585612 | |||
| ee798802e6 | |||
| 3f44bf0577 | |||
| eaedae0565 | |||
| 9c068d07c9 | |||
| 8abe88b2d3 | |||
| ece70df8fe | |||
| ecc375cf63 | |||
| d89a5b2e4b | |||
| d041586baf | |||
| 49472d32a1 | |||
| aa83b5c270 | |||
| 24bd37cd38 | |||
| a4e24baeb5 | |||
| f0512fdce3 | |||
| 101b6c9b74 | |||
| 9db3f91c28 | |||
| 94de4c07da | |||
| 67eedfddc4 | |||
| 829ec15b5b | |||
| e8bc314cfb | |||
| 85328a6e96 | |||
| 3af743ed1b | |||
| b3b67e7abf | |||
| f2d4464209 | |||
| 3fa52b6f32 | |||
| 01ad3e8a8a | |||
| fc4bbe2568 | |||
| d1a407c6b8 | |||
| a210ca3e17 | |||
| 276f4a5aa4 | |||
| 56b5e117f1 | |||
| 00c5630072 | |||
| f3a4594386 | |||
| ed335e5010 | |||
| cdd5dbb5ea | |||
| 0e51ca5bce | |||
| 0f24587641 | |||
| 5b1f74e263 | |||
| 0a2171ae91 | |||
| 09573a7cfc | |||
| 686c4f2698 | |||
| 9273950afc | |||
| add6fe3994 | |||
| 581efc0235 | |||
| b1d9b4810f | |||
| a868059124 | |||
| 2ceb788c2e | |||
| 3ca93dfc54 | |||
| e8315b207d | |||
| 3090536fc7 | |||
| c5034b43ab | |||
| 6b4b7939b0 | |||
| 99be7c765a | |||
| a018275ebf | |||
| fb9777247a | |||
| 1e8239c7e2 | |||
| 81b127648b | |||
| 4c5e3f2f28 | |||
| 9fb7d3ff42 | |||
| ac3093b5dc | |||
| 975d29fa31 | |||
| 94adb5f348 | |||
| fba8cc9acd | |||
| 0fb75fa95f | |||
| 840c4ac3a9 | |||
| 16613c86a7 | |||
| 0c43591378 | |||
| 9ea6ac8d24 | |||
| 394210c99b | |||
| 0a7c8c70b0 | |||
| 50cfffdb19 | |||
| 262f2ceef5 | |||
| 5377ca3d57 | |||
| 2fc48951c8 | |||
| b5308c583c | |||
| ffc4690cf5 | |||
| aace3488c5 | |||
| 9782ca679d | |||
| 258500d3f7 | |||
| 20a3a2d33d | |||
| 09b6d24303 | |||
| 244569004b | |||
| 773928cf47 | |||
| 0b60b46a40 | |||
| 4a4797e49d | |||
| a26a507d31 | |||
| 151578461e | |||
| c219e1c5e6 | |||
| 701190a853 | |||
| 4a5d224e26 | |||
| c948ed897f | |||
| 9f83c4003a | |||
| 60ad89af8d | |||
| f59c3ac6e4 | |||
| 25aae80175 | |||
| 8a784023df | |||
| d105b3086e | |||
| e68734af4d | |||
| 80aca5c42e | |||
| 91cc351b9f | |||
| 3727eff0a1 | |||
| 98e8f2c77f | |||
| d9cc0ef9d3 | |||
| 20e5e555f1 | |||
| 156a6a6cb6 | |||
| 2c57c4598c | |||
| 130678682c | |||
| ca706b0281 | |||
| 9ff2b454fb | |||
| 5362042060 | |||
| 59c05047e6 | |||
| dabc32defa | |||
| d0ba4497b8 | |||
| cbdbb2cb4f | |||
| 8090db41fd | |||
| e4b8bf49c6 | |||
| f91e4f7f47 | |||
| 2185ab4d96 | |||
| 8b55326dbd | |||
| 29de3846b7 | |||
| 781f6703f2 | |||
| 7901b11a6d | |||
| 136351e574 | |||
| 004fe34221 | |||
| dc15afe84c | |||
| 298b4baeae | |||
| efa72f8d96 | |||
| d5d8cca96e | |||
| 34676028f9 | |||
| 6639a57225 | |||
| e084da09e7 | |||
| 8afc6b5c10 | |||
| 28b558e0eb | |||
| 18d55d8ef9 | |||
| ded62cb3f1 | |||
| da00ab06c2 | |||
| 4021aeef49 | |||
| 3cf5653f0d | |||
| 779e44f63a | |||
| 17f8d7bb98 | |||
| 47e0d157a2 | |||
| bcfaf764a4 | |||
| cd38fe180c | |||
| 51b54d0dc6 | |||
| a452163d96 | |||
| 1457492697 | |||
| c7ce387900 | |||
| 7131dbd052 | |||
| 1a2c8df31e | |||
| d2d280c2e3 | |||
| 7480c912b5 | |||
| 307fb2c8a3 | |||
| 85dcab4226 | |||
| da7bb49726 | |||
| 17c5b9b811 | |||
| 458ca8b40c | |||
| 9bf906a371 | |||
| b1e7ce7277 | |||
| 0fb461e3df | |||
| fdb9083e95 | |||
| ab645f0fe9 | |||
| 290ce48514 | |||
| b6c35b9f76 | |||
| 5c90e911b0 | |||
| b63181ae6b | |||
| bc72822fe4 | |||
| 29d6e2157c | |||
| 6b851b5033 | |||
| cad2a32394 | |||
| b251ba9709 | |||
| 812fb1ff32 | |||
| 0e299769bb | |||
| 956fed6616 | |||
| ef982e5d22 | |||
| fe589090d7 | |||
| 6100570511 | |||
| c25370304e | |||
| 203d561a5d | |||
| 987367d1ca | |||
| f7523e073f | |||
| f42fd8a333 | |||
| bf39b6c79e | |||
| 33dbbb5178 | |||
| db79948886 | |||
| cf09b40b4a | |||
| fbc5aedf35 | |||
| 31c0f1566b | |||
| 16786404e1 | |||
| 050d68c0d1 | |||
| 9f807e8e1d | |||
| 851c2d0239 | |||
| f9ab417fdf | |||
| 6702e0714f | |||
| fa16558cda | |||
| 5328e0be5d | |||
| 1bd02406d4 | |||
| a4c34fb326 | |||
| 80a70b758d | |||
| 0a333efddb | |||
| a4cf1a2957 | |||
| 59e5709f28 | |||
| 1195eb23b1 | |||
| 69f00977ae | |||
| fc8b8b8a9a | |||
| 07a3160ed5 | |||
| 1496fbfa53 | |||
| 780593c0b6 | |||
| db891f43ce | |||
| 3357d9a86b | |||
| 43a7ca4b5e | |||
| c764a356ce | |||
| 4a881b9eaf | |||
| 76d70ea861 | |||
| 959a04e1cc | |||
| bf8fbcc9e3 | |||
| f9fe46154a | |||
| c97921b2ea | |||
| 95967f2f45 | |||
| 552c05599f | |||
| 837d110796 | |||
| fcc7e84300 | |||
| 9080bc85f2 | |||
| 512f1a8f4d | |||
| 4faa6b9af4 | |||
| 019ee02912 |
60
addons/cetmix_tower/README.rst
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
============
|
||||||
|
Cetmix Tower
|
||||||
|
============
|
||||||
|
|
||||||
|
..
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
|
!! changes will be overwritten. !!
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! source digest: sha256:ec1914ccdcdfd8bfa539b1c131e8b2c41946b9bf978d2a32d13ee89ab655cd65
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||||
|
:target: https://odoo-community.org/page/development-status
|
||||||
|
:alt: Beta
|
||||||
|
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
|
||||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
|
:alt: License: AGPL-3
|
||||||
|
.. |badge3| image:: https://img.shields.io/badge/github-cetmix%2Fcetmix--tower-lightgray.png?logo=github
|
||||||
|
:target: https://github.com/cetmix/cetmix-tower/tree/16.0/cetmix_tower
|
||||||
|
:alt: cetmix/cetmix-tower
|
||||||
|
|
||||||
|
|badge1| |badge2| |badge3|
|
||||||
|
|
||||||
|
This is a technical module that allows to get `Cetmix
|
||||||
|
Tower <https://cetmix.com/tower>`__ modules from the `Odoo App
|
||||||
|
Store <https://apps.odoo.com>`__.
|
||||||
|
|
||||||
|
It's designed to install all the `Cetmix
|
||||||
|
Tower <https://cetmix.com/tower>`__ modules at once.
|
||||||
|
|
||||||
|
**Table of contents**
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
===========
|
||||||
|
|
||||||
|
Bugs are tracked on `GitHub Issues <https://github.com/cetmix/cetmix-tower/issues>`_.
|
||||||
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
|
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||||
|
`feedback <https://github.com/cetmix/cetmix-tower/issues/new?body=module:%20cetmix_tower%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||||
|
|
||||||
|
Do not contact contributors directly about support or help with technical issues.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Authors
|
||||||
|
-------
|
||||||
|
|
||||||
|
* Cetmix
|
||||||
|
|
||||||
|
Maintainers
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This module is part of the `cetmix/cetmix-tower <https://github.com/cetmix/cetmix-tower/tree/16.0/cetmix_tower>`_ project on GitHub.
|
||||||
|
|
||||||
|
You are welcome to contribute.
|
||||||
0
addons/cetmix_tower/__init__.py
Normal file
22
addons/cetmix_tower/__manifest__.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Copyright Cetmix OU
|
||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
{
|
||||||
|
"name": "Cetmix Tower",
|
||||||
|
"summary": "Odoo SAAS Server Application Management",
|
||||||
|
"version": "16.0.2.1.0",
|
||||||
|
"development_status": "Beta",
|
||||||
|
"category": "Productivity",
|
||||||
|
"website": "https://tower.cetmix.com",
|
||||||
|
"live_test_url": "https://tower.cetmix.com/download",
|
||||||
|
"images": ["static/description/banner.png"],
|
||||||
|
"author": "Cetmix",
|
||||||
|
"license": "AGPL-3",
|
||||||
|
"application": True,
|
||||||
|
"installable": True,
|
||||||
|
"depends": [
|
||||||
|
"cetmix_tower_server",
|
||||||
|
"cetmix_tower_server_queue",
|
||||||
|
"cetmix_tower_git",
|
||||||
|
"cetmix_tower_webhook",
|
||||||
|
],
|
||||||
|
}
|
||||||
13
addons/cetmix_tower/i18n/cetmix_tower.pot
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 16.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: \n"
|
||||||
3
addons/cetmix_tower/pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["whool"]
|
||||||
|
build-backend = "whool.buildapi"
|
||||||
3
addons/cetmix_tower/readme/DESCRIPTION.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
This is a technical module that allows to get [Cetmix Tower](https://cetmix.com/tower) modules from the [Odoo App Store](https://apps.odoo.com).
|
||||||
|
|
||||||
|
It's designed to install all the [Cetmix Tower](https://cetmix.com/tower) modules at once.
|
||||||
BIN
addons/cetmix_tower/static/description/banner.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
435
addons/cetmix_tower/static/description/cetmix.svg
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 756 322.5" style="enable-background:new 0 0 756 322.5;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
.st1{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:0.75;stroke-miterlimit:10;}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M447.2,121.7c-2,1.6-4.7,2.9-8.3,3.9c-3.6,1-6.8,1.6-9.6,1.6c-10.9,0-19.5-2.8-25.8-8.3s-9.4-13.3-9.4-23.5
|
||||||
|
V53.7h-11.8V35.6H394V14.7l23.5-4.2v25.1h16l0.2,18l-16.2,0.1v37.4c0,4.6,1.1,8.4,3.3,11.3c2.2,3,5.4,4.5,9.7,4.5
|
||||||
|
c1.6,0,3.2-0.2,4.8-0.5c1.6-0.4,3.2-1,4.9-1.8L447.2,121.7z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M536.3,49.4c3.8-4.3,8.8-7.6,15-10.1c6.3-2.5,11.8-3.7,16.8-3.7c12,0,20.6,3.5,25.6,10.5
|
||||||
|
c5.1,7.1,7.6,15.6,7.6,25.8v55.2h-23.9v-54c0-5.6-1.3-9.9-4-12.8s-6.6-4.5-11.6-4.5c-3.4,0-7,0.9-10.7,2.7s-7.2,3.9-10.3,6.2
|
||||||
|
c0.1,1.2,0.2,2.4,0.4,3.7c0.1,1.2,0.2,2.4,0.2,3.7v55.2h-23.9V73.1c0-5.6-1.3-9.9-4-12.8s-6.5-4.5-11.4-4.5
|
||||||
|
c-3.1,0-6.1,0.6-9.2,1.9c-3.1,1.3-6.1,2.9-8.9,4.8v64.7h-23.7V39l23.3-3v7c3-2,5-3,9.4-4.5c5.7-1.9,10.7-3,15.2-3
|
||||||
|
c7,0,12.7,1.2,17.3,3.7C530.1,41.8,533.7,45.2,536.3,49.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M634.8,0c3.8,0,7,1.3,9.6,3.8c2.6,2.6,3.9,5.7,3.9,9.5s-1.3,6.9-3.9,9.5c-2.6,2.6-5.8,3.8-9.6,3.8
|
||||||
|
c-3.7,0-6.8-1.3-9.3-3.8c-2.6-2.6-3.8-5.7-3.8-9.5s1.3-6.9,3.8-9.5C628.1,1.3,631.2,0,634.8,0z M646.9,36l-0.2,91.1H623L622.9,36
|
||||||
|
H646.9z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M659.4,127.2l34.1-45.3l-32.8-45.8h25.8l21,29.9l21.9-29.9H756l-34.5,45.8l31.6,45.3H727l-20.5-28.4
|
||||||
|
l-20.9,28.4H659.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M198.4,81.5c0-26.6,19.5-45.5,46.7-45.5c18,0,29,7.8,34.8,15.6l-15.1,14c-4.2-6.1-10.5-9.4-18.6-9.4
|
||||||
|
c-14,0-23.9,10.1-23.9,25.2s9.9,25.4,23.9,25.4c8.1,0,14.4-3.6,18.6-9.4l15.1,13.8c-5.9,8.1-16.7,15.8-34.8,15.8
|
||||||
|
C217.9,127.2,198.4,108.3,198.4,81.5z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M289.4,81.5c0-25.2,18.8-45.5,45.6-45.5c26.3,0,44.1,19.4,44.1,47.8v5.1h-65.5c1.5,10.9,10.4,20.1,25.3,20.1
|
||||||
|
c7.5,0,17.8-3.2,23.5-8.7l10.3,14.7c-8.8,7.9-22.6,12-36.2,12C309.6,127.2,289.4,109.4,289.4,81.5z M334.8,54.2
|
||||||
|
c-14.4,0-20.6,10.6-21.7,19.2h43.4C356,65.2,350.2,54.2,334.8,54.2z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M92.3,91.5c0.6,0.3,0.7,1,0.2,1.5c-3.1,2.8-6.6,5.2-10.3,7.1c-6.6,3.2-13.7,4.8-20.9,4.7
|
||||||
|
c-7.2-0.3-14.2-2.2-20.3-5.7s-11.2-8.5-14.9-14.3s-5.8-12.5-6.4-19.3c-0.4-6.8,0.8-13.6,3.5-19.7c1.4-3,3.1-5.9,5.2-8.5
|
||||||
|
s4.5-4.9,7.1-7c5.2-4,11.3-6.7,17.7-7.8C46.9,24,41,27.1,36.4,31.4c-2.4,2.1-4.5,4.5-6.2,7.1c-1.8,2.6-3.1,5.5-4.3,8.4
|
||||||
|
c-2.1,5.8-2.7,12-1.9,18.1c0.9,6.1,3.3,11.7,6.8,16.6c3.6,4.8,8.3,8.7,13.6,11.2s11.1,3.6,16.9,3.4c5.8-0.3,11.3-2,16.2-4.9
|
||||||
|
c1.9-1.2,3.7-2.5,5.4-4c0.3-0.2,0.7-0.3,1-0.1L92.3,91.5z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M110.3,107.7c0.4,0.4,0.3,1.1-0.1,1.5c-0.2,0.2-0.4,0.4-0.6,0.6c-2.6,2.3-5.4,4.5-8.3,6.4s-6,3.6-9.2,5
|
||||||
|
s-6.5,2.6-9.8,3.5c-13.3,3.7-27.7,2.9-40.4-1.9s-23.8-13.5-31.3-24.7C3.2,86.8-0.4,73.5,0,60.3c0.5-13.1,5.2-26,13.1-36.1
|
||||||
|
C21.2,14,32.3,6.5,44.5,3C56.8-0.6,70-0.2,81.8,4c5.9,2.1,11.4,5.1,16.3,8.9c5,3.7,9.3,8.3,12.7,13.2c-3.8-4.8-8.3-9.1-13.3-12.4
|
||||||
|
S87,7.7,81.1,6c-11.5-3.4-24-2.9-35.3,1S24.9,18.4,18.3,28c-6.7,9.6-10.2,21.1-9.9,32.4c0.2,11.3,4.1,22.5,10.8,31.5
|
||||||
|
c6.7,8.9,16.2,15.5,26.7,18.8s21.9,3.1,32-0.3c8.2-2.7,15.4-7.5,21.2-13.5c0.4-0.4,1.1-0.5,1.5-0.1c0.6,0.6,1.3,1.3,1.8,2
|
||||||
|
C102.6,98.6,108.3,105.4,110.3,107.7z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M149.8,41l-35.6,43.5l2,4.1l4.1,8.6c0.7,1.5,0.4,3.3-0.8,4.5l-2.9,2.7c-0.2,0.2-0.4,0.2-0.7,0.2
|
||||||
|
c-0.1,0-0.2-0.1-0.3-0.2c0,0-0.1,0-0.1-0.1l-7.9-9.4c-0.8-0.9-1.6-1.8-2.5-2.6c-0.7-0.6-1.3-1.2-2.1-1.8c-1.4-1.1-3-2.2-4.6-3
|
||||||
|
c-0.2-0.2-0.5-0.3-0.8-0.4l-9.1-4.8l-1.7-0.9c-0.4-0.2-0.5-0.6-0.3-1l2-3.4c0.9-1.5,2.6-2.2,4.2-1.8l13.7,3.1l13.9-18L91.8,47.8
|
||||||
|
c-0.4-0.2-0.5-0.7-0.3-1l4.5-5.6c1.8-2.3,4.8-3.4,7.7-3l30.2,5.1l7.5-9.6c1.7-2.2,3.9-3.9,6.4-5l3.5-1.5c0.5-0.2,1-0.2,1.4-0.1
|
||||||
|
c0.3,0.1,0.5,0.2,0.7,0.3l0,0c0.5,0.4,0.9,1.2,0.7,2l-0.6,3.7C152.9,36,151.7,38.7,149.8,41z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M155.2,85.4l-4.5,5.6c-0.2,0.2-0.5,0.3-0.7,0.2c-0.1,0-0.3-0.1-0.4-0.2l-17.2-23.3l13.3-16.1l10.5,25.7
|
||||||
|
C157.5,79.9,157,83,155.2,85.4z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M397.4,241.7c-15.1,0-27.3-12.3-27.3-27.3s12.3-27.3,27.3-27.3c15.1,0,27.3,12.3,27.3,27.3l0,0
|
||||||
|
C424.7,229.5,412.4,241.7,397.4,241.7z M397.4,230.4c8.9,0,16-7.2,16-16c0-8.8-7.2-16-16-16c-8.9,0-16,7.2-16,16
|
||||||
|
C381.4,223.2,388.5,230.4,397.4,230.4z M340.2,241.7c-15.1,0-27.3-12.3-27.3-27.3s12.3-27.3,27.3-27.3c15.1,0,27.3,12.3,27.3,27.3
|
||||||
|
l0,0C367.5,229.5,355.3,241.7,340.2,241.7z M340.2,230.4c8.9,0,16-7.2,16-16c0-8.8-7.2-16-16-16c-8.9,0-16,7.2-16,16
|
||||||
|
C324.2,223.2,331.3,230.4,340.2,230.4z M310.3,214.1c0,15.3-12.3,27.7-27.3,27.7s-27.3-12.4-27.3-27.7s11.1-27,27.3-27
|
||||||
|
c6,0,11.5,1.2,16,4.6v-15.8c0-3,2.7-5.5,5.7-5.5s5.7,2.5,5.7,5.5V214.1z M282.9,230.4c8.9,0,16-7.2,16-16c0-8.8-7.2-16-16-16
|
||||||
|
c-8.9,0-16,7.2-16,16C266.9,223.2,274.1,230.4,282.9,230.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M225.7,241.7c-15.1,0-27.3-12.3-27.3-27.3s12.3-27.3,27.3-27.3s27.3,12.3,27.3,27.3l0,0
|
||||||
|
C253.1,229.5,240.9,241.7,225.7,241.7z M225.7,230.4c8.9,0,16-7.2,16-16c0-8.8-7.2-16-16-16c-8.9,0-16,7.2-16,16
|
||||||
|
C209.7,223.2,216.9,230.4,225.7,230.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M547.4,229.9c0.1,0,0.2,0,0.4,0c0.1,0,0.2,0.1,0.1,0.1v0.1c0,3.8,0,7.6,0,11.4c0,0.1,0,0.1,0,0.1
|
||||||
|
c-0.9,0-1.8,0-2.7,0c0-0.1,0-0.2,0-0.2c0-3.7,0-7.4,0-11.1c0-0.3-0.1-0.2,0.2-0.2c0.1,0,0.2,0,0.4,0
|
||||||
|
C546.4,229.9,546.9,229.9,547.4,229.9z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M520.6,200.7c-0.1,0.2-0.1,0.5-0.1,0.8c0,0.1-0.1,0.2-0.2,0.2c-0.4-0.1-0.9-0.1-1.3-0.2
|
||||||
|
c-1.5-0.2-3.2-0.4-4.7-0.6c-1.4-0.2-2.7-0.4-4.1-0.5c-0.4-0.1-0.8-0.1-1.2-0.2c-0.1-0.1-0.1-0.1-0.1-0.2c0.2-1.2,0.3-2.4,0.4-3.6
|
||||||
|
c0-0.1,0.1-0.2,0.2-0.2c1.1,0.1,2.1,0.2,3.2,0.4c1.2,0.2,2.6,0.4,3.7,0.5c1.4,0.2,2.7,0.4,4.1,0.5c0.4,0.1,0.4,0.1,0.3,0.4
|
||||||
|
c-0.1,0.3-0.1,0.6-0.1,1c-0.1,0-0.1,0.1-0.1,0.1c-0.1,0.4-0.1,0.9-0.2,1.4C520.6,200.7,520.6,200.7,520.6,200.7z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M572.9,206.4c0.1-0.2,0.1-0.6,0.1-0.8c0-0.1,0.1-0.1,0.2-0.1c1,0.1,2,0.2,3,0.4c1.1,0.1,2.1,0.2,3.2,0.4
|
||||||
|
c1.2,0.1,2.3,0.3,3.5,0.5c0.5,0.1,1.1,0.1,1.5,0.2c0.1,0,0.2,0.1,0.2,0.2c0.1,0.6-0.1,1.2-0.2,1.8c-0.1,0.6-0.2,1.2-0.2,1.7
|
||||||
|
c0,0.1-0.1,0.2-0.2,0.1c-1.4-0.2-2.8-0.4-4.2-0.6c-1.4-0.2-2.7-0.4-4-0.5c-1.1-0.1-2.1-0.2-3.1-0.4c-0.1,0-0.1-0.1-0.1-0.2
|
||||||
|
c0.1-0.4,0.1-0.7,0.1-1.1c0.1,0,0.1-0.1,0.1-0.1c0.1-0.4,0.1-0.9,0.2-1.3C573,206.6,573,206.5,572.9,206.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M571.1,213.7c0.1-0.2,0.2-0.4,0.3-0.7c0.1-0.1,0.1-0.1,0.2-0.1c2.2,1,4.4,2,6.6,3c1.2,0.6,2.4,1.1,3.7,1.7
|
||||||
|
c0.2,0.1,0.2,0.1,0.1,0.3c-0.5,1-0.9,2-1.4,3c-0.1,0.1-0.1,0.1-0.2,0.1c-2.3-1.1-4.6-2.1-7-3.2c-1.1-0.5-2.2-1-3.3-1.5
|
||||||
|
c-0.2-0.1-0.2-0.1-0.1-0.3c0.2-0.3,0.2-0.6,0.4-0.9c0.2-0.3,0.3-0.6,0.4-0.9C571,214.1,571.1,213.9,571.1,213.7z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M522.6,193.4c-0.1,0.2-0.2,0.5-0.4,0.7c-0.1,0.1-0.1,0.1-0.2,0.1c-1.2-0.6-2.6-1.1-3.8-1.7
|
||||||
|
c-1.5-0.7-3.2-1.4-4.8-2.2c-0.6-0.2-1.1-0.5-1.7-0.8c-0.1-0.1-0.1-0.1-0.1-0.2c0.5-1,1-2.1,1.4-3c0.1-0.1,0.1-0.1,0.2-0.1
|
||||||
|
c1,0.5,2.1,0.9,3,1.4c2.4,1.1,4.8,2.2,7.2,3.3c0.1,0.1,0.2,0.1,0.1,0.3c-0.1,0.2-0.2,0.5-0.4,0.8c-0.1,0.2-0.2,0.4-0.2,0.6
|
||||||
|
C522.8,192.7,522.6,193,522.6,193.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M567.2,220.1c0.1-0.2,0.2-0.3,0.4-0.5c0.1-0.1,0.1-0.1,0.2,0c2.1,1.9,4.3,3.7,6.5,5.6
|
||||||
|
c0.7,0.6,1.4,1.2,2.1,1.8c0.1,0.1,0.1,0.2,0.1,0.3c-0.7,0.8-1.4,1.5-2,2.4c-0.1,0.1-0.1,0.1-0.2,0c-1.9-1.7-3.9-3.3-5.8-5
|
||||||
|
c-0.9-0.8-1.8-1.6-2.8-2.4c-0.1-0.1-0.1-0.1,0-0.2c0.2-0.2,0.4-0.4,0.6-0.7"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M526.4,186.9c-0.2,0.2-0.4,0.4-0.5,0.6c-0.1,0.1-0.1,0.1-0.2,0c-1.2-1-2.4-2-3.5-3c-1.4-1.2-2.7-2.4-4.1-3.6
|
||||||
|
c-0.3-0.2-0.6-0.5-1-0.8c-0.1-0.1-0.1-0.1-0.1-0.2c0.7-0.8,1.4-1.5,2-2.3c0.1-0.1,0.2-0.1,0.2-0.1c1.4,1.2,2.7,2.4,4.1,3.6
|
||||||
|
c1.5,1.2,3,2.6,4.5,3.9c0.1,0.1,0.1,0.1,0.1,0.2c-0.2,0.2-0.3,0.4-0.4,0.5c-0.1,0-0.1,0-0.1,0.1
|
||||||
|
C527.1,186.1,526.8,186.5,526.4,186.9C526.4,186.8,526.4,186.9,526.4,186.9z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M531.8,181.9c-0.1,0.1-0.2,0.2-0.4,0.2c-0.1,0.1-0.2,0.1-0.3-0.1c-0.7-1.1-1.4-2.1-2-3.2
|
||||||
|
c-1.4-2.1-2.7-4.2-4-6.3c-0.1-0.1-0.1-0.2,0.1-0.2c0.8-0.5,1.6-1,2.4-1.5c0.1-0.1,0.2-0.1,0.2,0.1c1,1.5,2,3.1,3,4.7
|
||||||
|
c1,1.6,2.1,3.2,3,4.8c0.2,0.2,0.2,0.2-0.1,0.4c-0.1,0.1-0.2,0.1-0.3,0.2c-0.1,0-0.1,0.1-0.2,0.1c-0.4,0.2-0.8,0.5-1.1,0.7
|
||||||
|
C532,181.8,531.9,181.8,531.8,181.9z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M561.7,225.3c0.2-0.1,0.4-0.2,0.5-0.4c0.1-0.1,0.2-0.1,0.2,0.1c0.8,1.2,1.5,2.4,2.3,3.6
|
||||||
|
c1.1,1.7,2.2,3.4,3.3,5.1c0.2,0.2,0.4,0.5,0.5,0.8c0.1,0.1,0.1,0.2-0.1,0.2c-0.8,0.5-1.6,1.1-2.4,1.5c-0.1,0.1-0.2,0.1-0.2-0.1
|
||||||
|
c-1.7-2.7-3.4-5.3-5.1-8c-0.4-0.5-0.7-1.1-1-1.5c-0.1-0.1-0.1-0.1,0.1-0.2c0.2-0.1,0.4-0.2,0.6-0.4"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M554.9,228.6c0.2-0.1,0.4-0.1,0.5-0.1c0.1-0.1,0.2-0.1,0.2,0.1c0.4,1.4,0.8,2.9,1.2,4.3
|
||||||
|
c0.6,2.1,1.2,4.3,1.9,6.5c0.1,0.2,0.1,0.2-0.2,0.3c-0.8,0.2-1.6,0.5-2.4,0.7c-0.2,0.1-0.2,0-0.3-0.2c-0.4-1.2-0.7-2.4-1.1-3.6
|
||||||
|
c-0.7-2.4-1.4-4.8-2.1-7.1c-0.1-0.2-0.1-0.2,0.2-0.2c0.2-0.1,0.3-0.1,0.4-0.1C553.9,228.9,554.4,228.8,554.9,228.6z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M538.7,178.5c-0.2,0.1-0.4,0.1-0.5,0.1c-0.1,0.1-0.2,0-0.2-0.1c-0.2-0.8-0.5-1.6-0.7-2.4
|
||||||
|
c-0.8-2.6-1.5-5.2-2.3-7.9c-0.1-0.2-0.1-0.3-0.1-0.5c-0.1-0.1-0.1-0.2,0.1-0.2c0.7-0.2,1.5-0.4,2.2-0.7c0.5-0.1,0.4-0.1,0.6,0.3
|
||||||
|
c0.6,2,1.2,4,1.7,6c0.4,1.5,0.9,3,1.4,4.5c0.1,0.1,0,0.2-0.1,0.2c-0.2,0.1-0.3,0.1-0.4,0.1c-0.1-0.1-0.1,0-0.1,0
|
||||||
|
c-0.4,0.1-0.7,0.2-1.1,0.4C538.9,178.5,538.8,178.5,538.7,178.5z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M546.1,177.3c-0.5,0-0.5,0-0.5-0.5c0-3.6,0-7.3,0-11c0-0.2,0.1-0.2,0.2-0.2c0.8,0,1.5,0,2.4,0
|
||||||
|
c0.1,0,0.2,0.1,0.2,0.2c0,3.7,0,7.6,0,11.3c0,0.1-0.1,0.2-0.2,0.2c-0.1,0-0.3,0-0.4,0C547.2,177.2,546.7,177.2,546.1,177.3z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M540.1,229.1c0.4,0.1,0.4,0.1,0.2,0.4c-1,3.6-2.1,7.1-3,10.7c-0.1,0.2-0.1,0.2-0.2,0.1
|
||||||
|
c-0.7-0.2-1.4-0.4-2.1-0.6c-0.2-0.1-0.2-0.1-0.1-0.2c0.5-1.8,1.1-3.6,1.5-5.4c0.5-1.8,1.1-3.6,1.5-5.5c0.1-0.2,0.1-0.2,0.2-0.1
|
||||||
|
c0.1,0.1,0.2,0.1,0.4,0.1C539,228.8,539.6,228.9,540.1,229.1z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M553.6,178.2c-0.4-0.1-0.4-0.1-0.3-0.5c0.8-2.9,1.7-5.9,2.5-8.9c0.2-0.6,0.4-1.2,0.5-1.8
|
||||||
|
c0.1-0.1,0.1-0.2,0.2-0.1c0.7,0.2,1.4,0.4,2.2,0.6c0.2,0.1,0.1,0.1,0.1,0.2c-0.4,1.6-0.9,3.2-1.4,4.9c-0.6,2-1.1,4-1.7,6
|
||||||
|
c-0.1,0.2-0.1,0.2-0.3,0.2c-0.1-0.1-0.1-0.1-0.2-0.1c-0.3-0.1-0.7-0.2-1-0.3C554,178.2,553.8,178.2,553.6,178.2z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M560.3,181.2c-0.3-0.2-0.2-0.2-0.1-0.5c1.7-2.6,3.3-5.2,4.9-7.8c0.4-0.5,0.7-1.1,1-1.6
|
||||||
|
c0.1-0.1,0.1-0.1,0.2-0.1c0.6,0.4,1.1,0.7,1.7,1.1c0.1,0.1,0.1,0.1,0.1,0.2c-1,1.5-2,3-2.9,4.6c-1.1,1.6-2.1,3.2-3.1,4.9
|
||||||
|
c-0.1,0.2-0.2,0.2-0.4,0.1"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M533.1,226.1c0.2,0.2,0.2,0.2,0.1,0.5c-1.2,1.9-2.4,3.7-3.6,5.6c-0.8,1.2-1.6,2.5-2.4,3.7
|
||||||
|
c-0.1,0.1-0.2,0.2-0.3,0.1c-0.5-0.4-1.1-0.7-1.6-1.1c-0.1-0.1-0.1-0.1-0.1-0.2c1.4-2.1,2.7-4.3,4.1-6.4c0.7-1,1.2-2,1.9-3
|
||||||
|
c0.1-0.2,0.1-0.2,0.4-0.1c0,0,0.1,0,0.1,0.1C532.3,225.5,532.7,225.8,533.1,226.1z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M527.4,221.4c0.1,0.1,0.1,0.2-0.1,0.3c-2.7,2.4-5.4,4.7-8.2,7c-0.1,0.1-0.2,0.2-0.4,0.4
|
||||||
|
c-0.1,0.1-0.1,0.1-0.2,0c-0.4-0.5-0.8-0.9-1.2-1.4c-0.1-0.1-0.1-0.1,0.1-0.2c0.7-0.6,1.4-1.2,2.1-1.8c1.4-1.2,2.7-2.4,4.1-3.6
|
||||||
|
c0.8-0.7,1.6-1.4,2.4-2.1c0.2-0.1,0.2-0.1,0.3,0.1C526.7,220.6,527.1,221,527.4,221.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M566.2,185.8c-0.1-0.1-0.1-0.2,0.1-0.4c2.1-1.8,4.2-3.7,6.4-5.5c0.7-0.6,1.4-1.2,2.1-1.8
|
||||||
|
c0.1-0.1,0.2-0.1,0.2,0c0.4,0.4,0.7,0.9,1.1,1.3c0.1,0.1,0.1,0.2-0.1,0.2c-2.1,1.8-4.2,3.6-6.2,5.4c-0.7,0.7-1.5,1.2-2.2,1.9
|
||||||
|
c-0.1,0.1-0.2,0.1-0.4,0C566.9,186.6,566.6,186.2,566.2,185.8z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M570.3,191.9c-0.1-0.1,0.1-0.1,0.1-0.2c0.9-0.4,1.8-0.8,2.7-1.2c1.2-0.5,2.3-1.1,3.5-1.6
|
||||||
|
c1.4-0.6,2.7-1.2,4-1.9c0.1-0.1,0.2-0.1,0.3,0.1c0.2,0.4,0.4,0.9,0.6,1.3c0.1,0.1,0.1,0.2-0.1,0.2c-1.8,0.8-3.6,1.7-5.5,2.5
|
||||||
|
c-1.6,0.7-3.2,1.5-4.9,2.2c-0.1,0.1-0.2,0.1-0.2-0.1C570.8,192.9,570.6,192.4,570.3,191.9z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M523.2,215.2c0.1,0.1-0.1,0.1-0.1,0.2c-1,0.4-1.9,0.9-2.9,1.4c-1.2,0.6-2.4,1.1-3.6,1.7
|
||||||
|
c-1.3,0.6-2.6,1.2-3.9,1.8c-0.1,0.1-0.1,0.1-0.2-0.1c-0.2-0.5-0.4-1-0.7-1.4c-0.1-0.1-0.1-0.2,0.1-0.2c2.7-1.2,5.4-2.4,8-3.7
|
||||||
|
c0.7-0.4,1.5-0.7,2.3-1.1c0.1-0.1,0.2-0.1,0.2,0C522.7,214.2,523,214.8,523.2,215.2z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M531.8,181.9c0.1-0.1,0.1-0.1,0.2-0.2c0.4-0.2,0.7-0.5,1.1-0.7c0.1-0.1,0.1-0.1,0.2-0.1
|
||||||
|
c0.4,0.7,0.8,1.4,1.2,2c1,1.7,2,3.3,3,4.9c0.5,0.9,1.1,1.7,1.5,2.6c0.1,0.1,0.1,0.2-0.1,0.2c-0.4,0.2-0.9,0.5-1.2,0.8
|
||||||
|
c-0.1,0.1-0.1,0.1-0.2-0.1c-1-1.6-1.9-3.2-2.9-4.8c-0.7-1.1-1.4-2.2-2.1-3.4c-0.2-0.4-0.5-0.8-0.7-1.2
|
||||||
|
C532,182,531.9,182,531.8,181.9z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M514.2,209c-1.6,0.2-3.2,0.4-4.8,0.7c-0.1,0-0.2,0-0.2-0.1c-0.1-0.4-0.1-0.9-0.2-1.3c0-0.1,0.1-0.2,0.1-0.2
|
||||||
|
c1.3-0.2,2.6-0.4,3.9-0.5c1.5-0.2,3-0.4,4.5-0.6c1-0.1,2-0.2,3-0.4c0.1,0.6,0.2,1.1,0.2,1.6"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M572.8,199c1.4-0.2,2.9-0.4,4.4-0.6c1.3-0.2,2.7-0.4,4-0.5c1-0.1,2-0.2,3-0.4c0.2-0.1,0.2,0,0.2,0.2
|
||||||
|
c0.1,0.4,0.1,0.8,0.2,1.2c0,0.1,0,0.2-0.1,0.2c-1,0.1-1.9,0.2-2.9,0.4c-1.6,0.2-3.2,0.4-4.9,0.7c-1.2,0.2-2.4,0.3-3.6,0.5
|
||||||
|
c-0.1,0-0.1,0-0.1,0C572.9,200.1,572.8,199.5,572.8,199z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M546.1,177.3c0.5-0.1,1.1-0.1,1.6,0c0,3.7,0,7.4,0,11.1c0,0.2,0,0.2-0.2,0.2c-0.4,0-0.7,0-1.1,0
|
||||||
|
c-0.2,0-0.2-0.1-0.2-0.2c0-3.6,0-7.2,0-10.8C546.1,177.5,546.1,177.3,546.1,177.3z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M547.4,229.9c-0.5,0-1.1,0-1.6,0c0-3.7,0-7.4,0-11.1c0-0.2,0.1-0.2,0.2-0.2c0.4,0,0.8,0,1.2,0
|
||||||
|
c0.1,0,0.2,0.1,0.2,0.2c0,0.8,0,1.6,0,2.4C547.4,224.1,547.4,227,547.4,229.9z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M553.6,178.2c0.2,0,0.4,0.1,0.6,0.1c0.4,0.1,0.7,0.2,1,0.3c-0.1,0.2-0.2,0.5-0.2,0.8c-0.5,1.6-1,3.3-1.4,4.9
|
||||||
|
c-0.5,1.7-1,3.3-1.5,5c-0.1,0.1-0.1,0.2-0.2,0.1c-0.4-0.1-0.8-0.2-1.2-0.4c-0.1-0.1-0.1-0.1-0.1-0.2c0.4-1.2,0.7-2.4,1.1-3.7
|
||||||
|
c0.5-1.7,1-3.4,1.5-5.1C553.1,179.5,553.4,178.8,553.6,178.2z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M526.4,186.9c0-0.1,0-0.1,0.1-0.1c0.3-0.4,0.6-0.7,1-1.1c0.1-0.1,0.1-0.1,0.1-0.1c0.4,0.3,0.7,0.6,1,0.9
|
||||||
|
c0.5,0.5,1.1,1,1.5,1.4c1,0.9,2,1.8,3,2.7c0.6,0.6,1.2,1.1,1.8,1.7c0.2,0.2,0.5,0.5,0.8,0.7c0.1,0.1,0.1,0.1,0,0.2
|
||||||
|
c-0.3,0.3-0.6,0.7-0.8,1c-0.1,0.1-0.1,0.1-0.2,0.1c-0.3-0.3-0.6-0.6-1-0.8c-1.2-1.1-2.4-2.1-3.6-3.2c-1-0.9-1.9-1.7-2.9-2.6
|
||||||
|
C527.1,187.5,526.8,187.2,526.4,186.9z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M540.1,229.1c-0.5-0.1-1.1-0.2-1.5-0.4c0.4-1.1,0.7-2.3,1-3.4c0.4-1.4,0.8-2.7,1.2-4.1
|
||||||
|
c0.3-1.1,0.6-2.1,0.9-3.2c0.1-0.2,0.1-0.2,0.2-0.2c0.4,0.1,0.8,0.2,1.2,0.4c0.1,0.1,0.2,0.1,0.1,0.2c-0.5,1.7-1,3.4-1.5,5.1
|
||||||
|
c-0.5,1.7-1,3.5-1.5,5.2C540.1,228.8,540.1,228.9,540.1,229.1z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M522.6,193.4c0.1-0.3,0.2-0.6,0.4-0.9c0.1-0.2,0.1-0.4,0.2-0.6c0.7,0.3,1.3,0.6,2,0.9c1.5,0.7,3,1.4,4.5,2.1
|
||||||
|
c0.9,0.4,1.7,0.8,2.6,1.2c0.3,0.2,0.7,0.3,1,0.4c0.1,0.1,0.1,0.1,0.1,0.2c-0.2,0.4-0.4,0.8-0.5,1.2c-0.1,0.1-0.1,0.2-0.2,0.1
|
||||||
|
c-0.7-0.4-1.4-0.7-2.1-1c-1.9-0.9-3.9-1.8-5.8-2.7C524,194.1,523.3,193.7,522.6,193.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M571.1,213.7c-0.1,0.2-0.1,0.4-0.2,0.6c-0.1,0.3-0.2,0.6-0.4,0.9c-0.7-0.3-1.4-0.6-2.1-1
|
||||||
|
c-1.6-0.7-3.2-1.5-4.8-2.2c-0.8-0.4-1.6-0.7-2.4-1.1c-0.2-0.1-0.6-0.2-0.8-0.4c-0.1-0.1-0.1-0.1-0.1-0.2c0.2-0.4,0.4-0.8,0.6-1.2
|
||||||
|
c0.1-0.1,0.1-0.1,0.2-0.1c1.4,0.7,2.7,1.2,4.2,1.9C567.2,211.8,569.2,212.7,571.1,213.7C571.1,213.7,571.1,213.7,571.1,213.7z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M538.7,178.5c0.1-0.1,0.2-0.1,0.2-0.1c0.4-0.1,0.7-0.2,1.1-0.4c0.1,0,0.1-0.1,0.1,0c0.5,1.6,1,3.2,1.4,4.8
|
||||||
|
c0.4,1.2,0.7,2.4,1.1,3.6c0.2,0.7,0.4,1.5,0.7,2.2c0.1,0.1,0,0.2-0.1,0.2c-0.4,0.1-0.8,0.2-1.2,0.4c-0.2,0.1-0.2,0-0.2-0.2
|
||||||
|
c-0.3-1-0.6-2-0.9-3c-0.4-1.2-0.7-2.5-1.1-3.7C539.5,181.1,539.1,179.8,538.7,178.5z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M520.6,200.7c-0.1,0-0.1-0.1-0.1-0.1c0.1-0.4,0.1-0.9,0.2-1.4c0-0.1,0.1-0.1,0.1-0.1c0.9,0.1,1.7,0.2,2.6,0.4
|
||||||
|
c1,0.1,2.1,0.2,3,0.4c1,0.2,2,0.2,3,0.4c0.7,0.1,1.4,0.2,2.2,0.2c0.2,0,0.2,0.1,0.2,0.2c-0.1,0.4-0.1,0.8-0.2,1.2
|
||||||
|
c0,0.1-0.1,0.1-0.2,0.1c-0.9-0.1-1.8-0.2-2.7-0.4c-1-0.1-1.9-0.2-2.9-0.4c-0.9-0.1-1.8-0.2-2.7-0.4
|
||||||
|
C522.4,200.9,521.6,200.8,520.6,200.7C520.8,200.8,520.8,200.8,520.6,200.7z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M533.1,226.1c-0.4-0.3-0.9-0.6-1.4-0.9c0.3-0.4,0.5-0.8,0.8-1.2c0.7-1.1,1.4-2.1,2.1-3.2
|
||||||
|
c1.1-1.7,2.1-3.3,3.2-5c0.1-0.1,0.1-0.1,0.2-0.1c0.4,0.2,0.7,0.5,1.1,0.7c0.1,0.1,0.1,0.1,0.1,0.2c-0.5,0.7-0.9,1.4-1.4,2.2
|
||||||
|
c-1.5,2.4-3,4.7-4.5,7.1C533.2,226,533.2,226.1,533.1,226.1z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M560.4,181.1c0.5,0.2,0.9,0.6,1.4,0.9c-0.5,0.7-0.9,1.4-1.4,2.2c-1.5,2.4-3,4.8-4.6,7.2
|
||||||
|
c-0.1,0.1-0.1,0.1-0.2,0.1c-0.4-0.2-0.7-0.4-1.1-0.7c-0.1-0.1-0.2-0.1-0.1-0.2c0.8-1.2,1.5-2.4,2.3-3.6c1.2-1.9,2.4-3.8,3.6-5.7
|
||||||
|
C560.4,181.1,560.4,181.1,560.4,181.1z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M527.4,221.4c-0.4-0.4-0.7-0.8-1.1-1.2c1-0.9,2-1.7,3-2.6c1-0.9,2-1.7,3-2.6c0.8-0.7,1.7-1.4,2.5-2.1
|
||||||
|
c0.1-0.1,0.2-0.1,0.2,0.1c0.2,0.3,0.5,0.7,0.8,1c0.1,0.1,0.1,0.2,0,0.2c-0.6,0.5-1.2,1.1-1.8,1.6c-0.6,0.5-1.2,1.1-1.9,1.6
|
||||||
|
c-0.9,0.8-1.8,1.5-2.7,2.4C528.7,220.3,528,220.8,527.4,221.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M572.8,199c0.1,0.5,0.1,1.1,0.2,1.5c-0.4,0.1-0.7,0.1-1.1,0.2c-0.7,0.1-1.5,0.2-2.2,0.3
|
||||||
|
c-0.8,0.1-1.7,0.2-2.5,0.4c-0.8,0.1-1.7,0.2-2.5,0.4c-0.7,0.1-1.4,0.2-2.2,0.3c-0.2,0.1-0.4,0.1-0.7,0.1c-0.1,0.1-0.2-0.1-0.2-0.1
|
||||||
|
c-0.1-0.3-0.1-0.6-0.1-0.9c0-0.2-0.1-0.4,0-0.5c0.1-0.1,0.3-0.1,0.4-0.1c1.4-0.2,2.8-0.4,4.2-0.6c1.4-0.2,2.7-0.4,4-0.6
|
||||||
|
C571.2,199.2,571.9,199.2,572.8,199z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M554.9,228.8c-0.5,0.2-1,0.4-1.5,0.5c-0.2-1-0.6-2.2-0.9-3.2c-0.4-1.4-0.8-2.9-1.2-4.3
|
||||||
|
c-0.3-1.1-0.6-2.3-0.9-3.4c-0.1-0.1-0.1-0.2,0.1-0.2c0.4-0.1,0.8-0.2,1.2-0.4c0.1-0.1,0.2,0,0.2,0.1c0.4,1.5,0.8,3,1.2,4.5
|
||||||
|
c0.6,2,1.1,4,1.7,6.1C554.9,228.6,554.9,228.8,554.9,228.8z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M520.8,208.1c-0.1-0.5-0.1-1.1-0.2-1.5c1.2-0.2,2.4-0.4,3.6-0.5c1.1-0.1,2.2-0.3,3.3-0.5
|
||||||
|
c1.2-0.2,2.4-0.3,3.7-0.5c0.1,0,0.3-0.1,0.4-0.1c0.1,0,0.2,0,0.2,0.1c0.1,0.4,0.1,0.9,0.2,1.2c0,0.1-0.1,0.2-0.1,0.2
|
||||||
|
c-0.4,0.1-0.9,0.1-1.2,0.2c-0.7,0.1-1.4,0.2-2.2,0.3c-0.7,0.1-1.5,0.2-2.2,0.3c-0.7,0.1-1.4,0.2-2.1,0.3"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M566.3,221.4c-1.4-1.2-2.9-2.5-4.3-3.7c-1.1-1-2.2-1.9-3.4-2.9c-0.2-0.2-0.5-0.5-0.8-0.7
|
||||||
|
c-0.1-0.1-0.1-0.1,0-0.2c0.2-0.3,0.5-0.6,0.8-0.9c0.1-0.1,0.2-0.1,0.3,0c0.4,0.4,0.9,0.8,1.4,1.2c0.9,0.7,1.7,1.5,2.6,2.3
|
||||||
|
c0.7,0.6,1.4,1.2,2,1.7c0.8,0.8,1.5,1.4,2.3,2.1c0,0.1-0.1,0.2-0.2,0.2"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M572.9,206.4c0.1,0.1,0.1,0.1,0.1,0.2c-0.1,0.4-0.1,0.9-0.2,1.3c0,0.1,0,0.1-0.1,0.1
|
||||||
|
c-0.4-0.1-0.9-0.1-1.4-0.2c-0.8-0.1-1.7-0.2-2.6-0.4c-0.7-0.1-1.4-0.2-2.2-0.3c-0.7-0.1-1.4-0.2-2.2-0.3c-0.7-0.1-1.2-0.2-1.9-0.2
|
||||||
|
c-0.2-0.1-0.6-0.1-0.8-0.1c-0.1,0-0.1-0.1-0.1-0.2c0.1-0.4,0.1-0.9,0.2-1.3c0-0.1,0.1-0.1,0.2-0.1c0.4,0.1,0.7,0.1,1.1,0.1
|
||||||
|
c0.8,0.1,1.6,0.2,2.4,0.4c0.7,0.1,1.5,0.2,2.3,0.3c0.8,0.1,1.7,0.2,2.5,0.4c0.8,0.1,1.6,0.2,2.4,0.4
|
||||||
|
C572.7,206.4,572.8,206.4,572.9,206.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M566.2,185.8c0.4,0.4,0.7,0.8,1.1,1.2c-0.4,0.3-0.7,0.6-1.1,0.9c-0.9,0.8-1.8,1.5-2.8,2.4
|
||||||
|
c-0.8,0.7-1.7,1.4-2.4,2.1c-0.7,0.6-1.4,1.2-2.1,1.8c-0.1,0.1-0.2,0.1-0.4-0.1c-0.2-0.3-0.5-0.6-0.8-0.9c-0.1-0.1-0.1-0.2,0-0.2
|
||||||
|
c0.5-0.4,1-0.9,1.5-1.3c0.6-0.5,1.2-1.1,1.8-1.5c0.9-0.8,1.9-1.6,2.8-2.4C564.7,187.1,565.5,186.4,566.2,185.8z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M561.7,225.3c-0.1,0.2-0.4,0.2-0.6,0.4c-0.2,0.1-0.4,0.2-0.6,0.4c-0.1,0.1-0.1,0.1-0.2,0.1
|
||||||
|
c-0.2-0.5-0.6-1-0.9-1.5c-1.1-1.7-2.1-3.5-3.2-5.2c-0.6-0.9-1.1-1.8-1.7-2.7c-0.1-0.1-0.1-0.2,0.1-0.2c0.4-0.2,0.7-0.4,1.1-0.7
|
||||||
|
c0.1-0.1,0.2-0.1,0.2,0.1c1.5,2.4,3,4.9,4.5,7.4C560.8,223.9,561.2,224.6,561.7,225.3z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M523.2,215.2c-0.2-0.5-0.5-1-0.7-1.4c0.7-0.4,1.4-0.7,2.2-1c2-0.9,4-1.8,6-2.7c0.6-0.3,1.2-0.6,1.9-0.9
|
||||||
|
c0.1-0.1,0.2-0.1,0.2,0.1c0.2,0.4,0.4,0.8,0.5,1.2c0.1,0.1,0.1,0.1-0.1,0.2c-2.1,0.9-4,1.9-6.1,2.8c-1.1,0.5-2.3,1.1-3.4,1.5
|
||||||
|
C523.6,215.1,523.4,215.2,523.2,215.2z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M570.3,191.9c0.2,0.5,0.5,1,0.7,1.4c-0.4,0.2-0.7,0.4-1.1,0.5c-1,0.5-2,0.9-3,1.4c-1.5,0.7-3,1.4-4.6,2.1
|
||||||
|
c-0.5,0.2-1,0.4-1.5,0.7c-0.1,0.1-0.2,0.1-0.2-0.1c-0.2-0.4-0.4-0.8-0.6-1.2c-0.1-0.1,0-0.1,0.1-0.2c1.6-0.7,3.2-1.5,4.9-2.2
|
||||||
|
c1.2-0.5,2.3-1.1,3.5-1.6c0.6-0.2,1.1-0.5,1.7-0.7C570.3,192,570.3,192,570.3,191.9z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M599.3,214.7c0-8.1,2.2-14.5,6.5-19c4.3-4.6,9.9-6.8,16.8-6.8c4.5,0,8.6,1.1,12.2,3.2c3.6,2.1,6.4,5.2,8.3,9
|
||||||
|
c1.9,3.9,2.9,8.2,2.9,13.1c0,4.9-1,9.3-3,13.3c-2,3.9-4.8,6.8-8.5,8.9c-3.6,2-7.6,3-11.8,3c-4.6,0-8.7-1.1-12.3-3.3
|
||||||
|
c-3.6-2.2-6.4-5.2-8.2-9.1C600.2,223,599.3,219,599.3,214.7z M605.9,214.8c0,5.9,1.6,10.5,4.8,13.9c3.2,3.4,7.1,5.1,11.9,5.1
|
||||||
|
c4.9,0,8.9-1.7,12-5.1c3.2-3.4,4.7-8.3,4.7-14.5c0-4-0.7-7.4-2-10.4s-3.3-5.2-5.9-6.9c-2.6-1.6-5.5-2.4-8.7-2.4
|
||||||
|
c-4.6,0-8.5,1.5-11.8,4.7C607.6,202.2,605.9,207.4,605.9,214.8z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M622.7,241.4c-4.9,0-9.4-1.2-13.4-3.6s-7-5.8-9-10c-2-4-3-8.3-3-13.1c0-8.6,2.4-15.5,7.1-20.4
|
||||||
|
c4.7-4.9,10.9-7.5,18.3-7.5c4.9,0,9.3,1.2,13.3,3.5c3.9,2.3,7,5.6,9.1,9.8c2,4.2,3.1,8.9,3.1,14s-1.1,9.9-3.2,14.2
|
||||||
|
c-2.3,4.3-5.3,7.6-9.3,9.8C631.6,240.3,627.3,241.4,622.7,241.4z M622.7,191c-6.4,0-11.4,2-15.4,6.2c-4,4.1-6,10.1-6,17.6
|
||||||
|
c0,4.1,0.8,7.8,2.6,11.2c1.7,3.5,4.2,6.3,7.4,8.3c3.3,2,7.1,3,11.2,3c3.9,0,7.6-1,10.8-2.8c3.3-1.8,5.8-4.5,7.7-8
|
||||||
|
c1.8-3.6,2.7-7.8,2.7-12.3c0-4.6-0.9-8.6-2.6-12.2c-1.7-3.5-4.3-6.2-7.4-8.1C630.5,191.9,626.9,191,622.7,191z M622.6,235.8
|
||||||
|
c-5.4,0-9.9-1.9-13.5-5.7c-3.5-3.7-5.3-8.9-5.3-15.3c0-7.9,1.9-13.7,5.7-17.3c3.6-3.5,8.1-5.3,13.2-5.3c3.6,0,6.9,1,9.8,2.7
|
||||||
|
c2.9,1.9,5.2,4.5,6.7,7.8c1.5,3.2,2.2,6.9,2.2,11.3c0,6.8-1.8,12.2-5.2,16C632.6,233.8,628,235.8,622.6,235.8z M622.7,196.4
|
||||||
|
c-4.1,0-7.4,1.4-10.4,4.1c-2.9,2.8-4.3,7.6-4.3,14.3c0,5.3,1.4,9.5,4.2,12.4c2.7,3,6.1,4.4,10.4,4.4s7.7-1.4,10.5-4.5
|
||||||
|
c2.7-3,4.2-7.4,4.2-13.1c0-3.7-0.6-6.8-1.8-9.5c-1.2-2.6-2.9-4.5-5.1-6C628.1,197.1,625.5,196.4,622.7,196.4z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M693.7,221.3l6.4,1.6c-1.4,5.3-3.8,9.3-7.3,12.1s-7.8,4.2-12.9,4.2c-5.2,0-9.5-1.1-12.8-3.2
|
||||||
|
c-3.3-2.1-5.8-5.2-7.5-9.3c-1.7-4-2.6-8.4-2.6-13.1c0-5.1,1-9.5,2.9-13.3c2-3.8,4.7-6.7,8.3-8.6s7.5-2.9,11.8-2.9
|
||||||
|
c4.9,0,9,1.2,12.3,3.7c3.3,2.5,5.7,6,7,10.5l-6.4,1.5c-1.1-3.6-2.8-6.1-4.9-7.7c-2.1-1.6-4.9-2.4-8.1-2.4c-3.7,0-6.9,0.9-9.4,2.7
|
||||||
|
c-2.5,1.8-4.3,4.2-5.3,7.3c-1,3-1.5,6.1-1.5,9.3c0,4.2,0.6,7.8,1.8,10.9c1.2,3.1,3.1,5.4,5.7,7c2.6,1.5,5.3,2.3,8.3,2.3
|
||||||
|
c3.6,0,6.7-1.1,9.2-3.2C691,228.5,692.8,225.4,693.7,221.3z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M679.9,241.3c-5.7,0-10.4-1.2-13.9-3.6c-3.6-2.4-6.4-5.8-8.3-10.2c-1.8-4.2-2.7-8.9-2.7-13.9
|
||||||
|
c0-5.5,1.1-10.2,3.2-14.2c2.1-4.2,5.2-7.3,9.1-9.5c3.8-2.1,8.2-3.2,12.9-3.2c5.3,0,9.9,1.4,13.6,4.2c3.7,2.8,6.3,6.7,7.7,11.5
|
||||||
|
l0.6,2.1l-10.4,2.4l-0.6-1.8c-1-3.1-2.4-5.4-4.2-6.7c-1.8-1.4-4.1-2-6.8-2c-3.3,0-6.1,0.8-8.2,2.3c-2.1,1.5-3.7,3.6-4.5,6.2
|
||||||
|
c-1,2.9-1.4,5.8-1.4,8.7c0,3.9,0.5,7.3,1.7,10.1c1,2.6,2.6,4.6,4.8,6c2.3,1.4,4.6,2,7.2,2c3.1,0,5.7-0.9,7.9-2.7
|
||||||
|
c2.1-1.8,3.6-4.6,4.4-8.2l0.5-2.1l10.5,2.6l-0.5,2c-1.5,5.7-4.2,10.2-8,13.3C690.2,239.8,685.4,241.3,679.9,241.3z M680,190.9
|
||||||
|
c-4,0-7.6,0.9-10.8,2.7c-3.2,1.8-5.7,4.3-7.4,7.7c-1.8,3.4-2.7,7.6-2.7,12.3c0,4.5,0.8,8.5,2.4,12.3c1.5,3.6,3.8,6.4,6.7,8.3
|
||||||
|
s6.8,2.9,11.7,2.9c4.7,0,8.5-1.2,11.6-3.7c2.7-2.1,4.7-5.2,6-9l-2.3-0.6c-1.1,3.5-2.9,6.3-5.2,8.4c-2.9,2.4-6.4,3.6-10.5,3.6
|
||||||
|
c-3.3,0-6.5-0.9-9.3-2.6c-3-1.8-5.2-4.5-6.5-8c-1.2-3.3-1.9-7.3-1.9-11.7c0-3.4,0.5-6.7,1.7-10c1.1-3.5,3.2-6.2,6.1-8.3
|
||||||
|
c2.9-2,6.4-3.1,10.6-3.1c3.7,0,6.8,1,9.3,2.9c2.1,1.6,3.8,3.9,5,7l2.3-0.5c-1.2-3-3-5.5-5.5-7.3C688.1,192,684.4,190.9,680,190.9
|
||||||
|
z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M707.2,238.5l18.7-48.8h7l19.9,48.8h-7.4l-5.7-14.8h-20.4l-5.4,14.8H707.2z M721.3,218.4h16.5l-5.1-13.5
|
||||||
|
c-1.5-4.1-2.7-7.5-3.5-10.1c-0.6,3.2-1.5,6.2-2.6,9.3L721.3,218.4z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M729.4,187.6h5l21.7,53h-11.9l-5.7-14.8h-17.5l-5.4,14.8h-11.2l20.2-53h4 M746.9,236.4h2.9l-18.2-44.6h-1
|
||||||
|
l0.7,2.4c0.8,2.9,2,6.3,3.4,10l6.2,16.3h-22.6l6.4-17.1c1.1-3.2,2-6.1,2.5-9l0.5-2.7h-0.4l-17.1,44.6h2.3l5.4-14.8h23.3
|
||||||
|
L746.9,236.4z M724.4,216.3h10.5l-4-10.7c-0.4-1.2-0.8-2.3-1.2-3.5c-0.3,0.9-0.6,1.7-0.9,2.6L724.4,216.3z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st1" d="M327.3,257c1.3-0.2,3-0.4,5.3-0.4c2.7,0,4.7,0.7,6,1.8c1.2,1,1.8,2.6,1.8,4.5c0,1.9-0.6,3.5-1.7,4.5
|
||||||
|
c-1.4,1.5-3.8,2.3-6.5,2.3c-0.8,0-1.5-0.1-2.2-0.2v8.5h-2.7V257z M330,267.3c0.6,0.2,1.4,0.2,2.3,0.2c3.3,0,5.4-1.6,5.4-4.5
|
||||||
|
c0-2.8-2-4.2-5-4.2c-1.2,0-2.1,0.1-2.6,0.2L330,267.3L330,267.3z"/>
|
||||||
|
<path class="st1" d="M352,278l-0.2-1.9h-0.1c-0.8,1.2-2.5,2.3-4.6,2.3c-3.1,0-4.6-2.2-4.6-4.4c0-3.7,3.3-5.7,9.2-5.7V268
|
||||||
|
c0-1.2-0.4-3.5-3.5-3.5c-1.4,0-2.9,0.4-4,1.1l-0.7-1.8c1.2-0.8,3.1-1.4,5-1.4c4.6,0,5.8,3.2,5.8,6.2v5.7c0,1.3,0.1,2.6,0.2,3.6H352
|
||||||
|
L352,278z M351.6,270.2c-3-0.1-6.5,0.5-6.5,3.5c0,1.8,1.2,2.7,2.6,2.7c2,0,3.3-1.2,3.7-2.6c0.1-0.3,0.2-0.6,0.2-0.9V270.2z"/>
|
||||||
|
<path class="st1" d="M359.2,267.6c0-1.8-0.1-3.3-0.1-4.8h2.4l0.1,3h0.1c0.7-2,2.4-3.3,4.2-3.3c0.3,0,0.5,0.1,0.8,0.1v2.6
|
||||||
|
c-0.3-0.1-0.6-0.1-1-0.1c-2,0-3.3,1.5-3.7,3.6c-0.1,0.4-0.1,0.8-0.1,1.3v8.1h-2.7V267.6z"/>
|
||||||
|
<path class="st1" d="M374.1,258.4v4.4h4v2.1h-4v8.2c0,1.9,0.5,3,2.1,3c0.7,0,1.2-0.1,1.6-0.2l0.1,2.1c-0.5,0.2-1.4,0.4-2.4,0.4
|
||||||
|
c-1.3,0-2.3-0.4-3-1.2c-0.8-0.8-1.1-2.2-1.1-4v-8.3H369v-2.1h2.4v-3.6L374.1,258.4z"/>
|
||||||
|
<path class="st1" d="M381.6,266.9c0-1.5-0.1-2.9-0.1-4.1h2.4l0.2,2.5h0.1c0.8-1.4,2.5-2.9,5.1-2.9c2.1,0,5.4,1.2,5.4,6.5v9.1h-2.8
|
||||||
|
v-8.8c0-2.4-0.9-4.5-3.5-4.5c-1.8,0-3.3,1.3-3.7,2.9c-0.1,0.4-0.2,0.8-0.2,1.3v9.2h-2.8L381.6,266.9L381.6,266.9z"/>
|
||||||
|
<path class="st1" d="M401.1,270.9c0.1,3.7,2.4,5.3,5.2,5.3c2,0,3.2-0.4,4.2-0.8l0.5,2c-1,0.4-2.7,1-5.1,1c-4.7,0-7.5-3.1-7.5-7.7
|
||||||
|
c0-4.6,2.7-8.2,7.1-8.2c5,0,6.3,4.4,6.3,7.2c0,0.6-0.1,1-0.1,1.3L401.1,270.9L401.1,270.9L401.1,270.9z M409.2,268.9
|
||||||
|
c0.1-1.8-0.7-4.5-3.9-4.5c-2.8,0-4,2.6-4.3,4.5H409.2z"/>
|
||||||
|
<path class="st1" d="M415.8,267.6c0-1.8-0.1-3.3-0.1-4.8h2.4l0.1,3h0.1c0.7-2,2.4-3.3,4.2-3.3c0.3,0,0.5,0.1,0.8,0.1v2.6
|
||||||
|
c-0.3-0.1-0.6-0.1-1-0.1c-2,0-3.3,1.5-3.7,3.6c-0.1,0.4-0.1,0.8-0.1,1.3v8.1h-2.7V267.6z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st1" d="M614.7,277.4c-1,0.5-3,1-5.6,1c-6,0-10.5-3.8-10.5-10.8c0-6.7,4.5-11.2,11.1-11.2c2.7,0,4.3,0.6,5.1,1
|
||||||
|
l-0.7,2.3c-1-0.5-2.5-0.9-4.3-0.9c-5,0-8.3,3.2-8.3,8.7c0,5.2,3,8.6,8.2,8.6c1.7,0,3.4-0.4,4.5-0.9L614.7,277.4z"/>
|
||||||
|
<path class="st1" d="M631.9,270.3c0,5.7-3.9,8.1-7.6,8.1c-4.1,0-7.3-3-7.3-7.9c0-5.1,3.3-8.1,7.6-8.1
|
||||||
|
C629,262.4,631.9,265.6,631.9,270.3z M619.9,270.5c0,3.3,1.9,5.9,4.6,5.9c2.7,0,4.6-2.5,4.6-6c0-2.6-1.3-5.9-4.6-5.9
|
||||||
|
C621.2,264.5,619.9,267.6,619.9,270.5z"/>
|
||||||
|
<path class="st1" d="M635.9,266.9c0-1.5-0.1-2.9-0.1-4.1h2.4l0.2,2.5h0.1c0.8-1.4,2.5-2.9,5.1-2.9c2.1,0,5.4,1.2,5.4,6.5v9.1h-2.8
|
||||||
|
v-8.8c0-2.4-0.9-4.5-3.5-4.5c-1.8,0-3.3,1.3-3.7,2.9c-0.1,0.4-0.2,0.8-0.2,1.3v9.2h-2.8V266.9z"/>
|
||||||
|
<path class="st1" d="M657.1,258.4v4.4h4v2.1h-4v8.2c0,1.9,0.5,3,2.1,3c0.7,0,1.2-0.1,1.6-0.2l0.1,2.1c-0.5,0.2-1.4,0.4-2.4,0.4
|
||||||
|
c-1.3,0-2.3-0.4-3-1.2c-0.8-0.8-1.1-2.2-1.1-4v-8.3H652v-2.1h2.4v-3.6L657.1,258.4z"/>
|
||||||
|
<path class="st1" d="M664.6,267.6c0-1.8-0.1-3.3-0.1-4.8h2.4l0.1,3h0.1c0.7-2,2.4-3.3,4.2-3.3c0.3,0,0.5,0.1,0.8,0.1v2.6
|
||||||
|
c-0.3-0.1-0.6-0.1-1-0.1c-2,0-3.3,1.5-3.7,3.6c-0.1,0.4-0.1,0.8-0.1,1.3v8.1h-2.7V267.6z"/>
|
||||||
|
<path class="st1" d="M678.5,258.5c0.1,1-0.7,1.7-1.8,1.7c-1,0-1.7-0.8-1.7-1.7s0.7-1.7,1.7-1.7
|
||||||
|
C677.9,256.8,678.5,257.5,678.5,258.5z M675.4,278v-15.2h2.8V278H675.4z"/>
|
||||||
|
<path class="st1" d="M683.2,278c0.1-1,0.1-2.6,0.1-3.9v-18.4h2.7v9.6h0.1c1-1.7,2.7-2.8,5.2-2.8c3.8,0,6.5,3.2,6.4,7.8
|
||||||
|
c0,5.5-3.5,8.2-6.8,8.2c-2.2,0-4-0.8-5.1-2.9h-0.1l-0.1,2.5H683.2z M686,271.9c0,0.4,0.1,0.7,0.1,1c0.5,1.9,2.1,3.3,4.2,3.3
|
||||||
|
c2.9,0,4.6-2.4,4.6-5.9c0-3-1.6-5.7-4.5-5.7c-1.9,0-3.6,1.3-4.2,3.4c-0.1,0.3-0.2,0.7-0.2,1.1L686,271.9L686,271.9z"/>
|
||||||
|
<path class="st1" d="M714.4,273.9c0,1.5,0.1,3,0.1,4.2h-2.4l-0.2-2.5h-0.1c-0.7,1.2-2.3,2.9-5.1,2.9c-2.4,0-5.2-1.3-5.2-6.7v-8.9
|
||||||
|
h2.8v8.5c0,2.9,0.9,4.9,3.4,4.9c1.8,0,3.2-1.3,3.6-2.5c0.2-0.4,0.2-0.9,0.2-1.4v-9.3h2.8L714.4,273.9L714.4,273.9z"/>
|
||||||
|
<path class="st1" d="M722.8,258.4v4.4h4v2.1h-4v8.2c0,1.9,0.5,3,2.1,3c0.7,0,1.2-0.1,1.6-0.2l0.1,2.1c-0.5,0.2-1.4,0.4-2.4,0.4
|
||||||
|
c-1.3,0-2.3-0.4-3-1.2c-0.8-0.8-1.1-2.2-1.1-4v-8.3h-2.4v-2.1h2.4v-3.6L722.8,258.4z"/>
|
||||||
|
<path class="st1" d="M744,270.3c0,5.7-3.9,8.1-7.6,8.1c-4.1,0-7.3-3-7.3-7.9c0-5.1,3.3-8.1,7.6-8.1C741,262.4,744,265.6,744,270.3z
|
||||||
|
M731.9,270.5c0,3.3,1.9,5.9,4.6,5.9c2.7,0,4.6-2.5,4.6-6c0-2.6-1.3-5.9-4.6-5.9C733.3,264.5,731.9,267.6,731.9,270.5z"/>
|
||||||
|
<path class="st1" d="M747.9,267.6c0-1.8-0.1-3.3-0.1-4.8h2.4l0.1,3h0.1c0.7-2,2.4-3.3,4.2-3.3c0.3,0,0.5,0.1,0.8,0.1v2.6
|
||||||
|
c-0.3-0.1-0.6-0.1-1-0.1c-2,0-3.3,1.5-3.7,3.6c-0.1,0.4-0.1,0.8-0.1,1.3v8.1h-2.7V267.6L747.9,267.6z"/>
|
||||||
|
<path class="st1" d="M646.9,312.6c1.2,0.8,3,1.4,4.9,1.4c2.8,0,4.5-1.5,4.5-3.6c0-2-1.1-3.1-4-4.2c-3.5-1.2-5.6-3-5.6-6
|
||||||
|
c0-3.3,2.7-5.8,6.9-5.8c2.2,0,3.7,0.5,4.7,1l-0.8,2.3c-0.7-0.4-2.1-1-4-1c-2.9,0-4,1.7-4,3.2c0,2,1.3,3,4.2,4.1
|
||||||
|
c3.6,1.4,5.4,3.1,5.4,6.2c0,3.3-2.4,6.1-7.4,6.1c-2,0-4.3-0.6-5.4-1.4L646.9,312.6z"/>
|
||||||
|
<path class="st1" d="M663.2,305.6c0-2-0.1-3.5-0.1-5h2.5l0.1,2.6h0.1c1.1-1.8,2.9-3,5.4-3c3.7,0,6.5,3.1,6.5,7.7
|
||||||
|
c0,5.5-3.3,8.2-7,8.2c-2,0-3.8-0.9-4.7-2.4h-0.1v8.3h-2.7V305.6z M665.9,309.7c0,0.4,0.1,0.8,0.1,1.1c0.5,1.9,2.2,3.3,4.2,3.3
|
||||||
|
c2.9,0,4.6-2.4,4.6-5.9c0-3-1.6-5.7-4.5-5.7c-1.9,0-3.6,1.4-4.2,3.5c-0.1,0.4-0.2,0.8-0.2,1.1L665.9,309.7L665.9,309.7z"/>
|
||||||
|
<path class="st1" d="M695.4,308.1c0,5.7-3.9,8.1-7.6,8.1c-4.1,0-7.3-3-7.3-7.9c0-5.1,3.3-8.1,7.6-8.1
|
||||||
|
C692.4,300.3,695.4,303.4,695.4,308.1z M683.2,308.3c0,3.3,1.9,5.9,4.6,5.9c2.7,0,4.6-2.5,4.6-6c0-2.6-1.3-5.9-4.6-5.9
|
||||||
|
C684.7,302.4,683.2,305.4,683.2,308.3z"/>
|
||||||
|
<path class="st1" d="M699.3,304.8c0-1.5-0.1-2.9-0.1-4.1h2.4l0.2,2.5h0.1c0.8-1.4,2.5-2.9,5.1-2.9c2.1,0,5.4,1.2,5.4,6.5v9.1h-2.8
|
||||||
|
v-8.8c0-2.4-0.9-4.5-3.5-4.5c-1.8,0-3.3,1.3-3.7,2.9c-0.1,0.4-0.2,0.8-0.2,1.3v9.2h-2.8L699.3,304.8L699.3,304.8z"/>
|
||||||
|
<path class="st1" d="M716.9,313.1c0.8,0.5,2.3,1.1,3.6,1.1c2,0,3-1,3-2.3c0-1.3-0.8-2-2.9-2.8c-2.7-1-4-2.5-4-4.3
|
||||||
|
c0-2.4,2-4.5,5.2-4.5c1.5,0,2.9,0.4,3.7,1l-0.7,2c-0.6-0.4-1.7-0.9-3.1-0.9c-1.7,0-2.6,1-2.6,2.1c0,1.2,0.9,1.8,2.9,2.6
|
||||||
|
c2.7,1,4,2.3,4,4.6c0,2.7-2.1,4.6-5.7,4.6c-1.7,0-3.2-0.4-4.3-1L716.9,313.1z"/>
|
||||||
|
<path class="st1" d="M744,308.1c0,5.7-3.9,8.1-7.6,8.1c-4.1,0-7.3-3-7.3-7.9c0-5.1,3.3-8.1,7.6-8.1C741,300.3,744,303.4,744,308.1z
|
||||||
|
M731.9,308.3c0,3.3,1.9,5.9,4.6,5.9c2.7,0,4.6-2.5,4.6-6c0-2.6-1.3-5.9-4.6-5.9C733.3,302.4,731.9,305.4,731.9,308.3z"/>
|
||||||
|
<path class="st1" d="M747.9,305.4c0-1.8-0.1-3.3-0.1-4.8h2.4l0.1,3h0.1c0.7-2,2.4-3.3,4.2-3.3c0.3,0,0.5,0.1,0.8,0.1v2.6
|
||||||
|
c-0.3-0.1-0.6-0.1-1-0.1c-2,0-3.3,1.5-3.7,3.6c-0.1,0.4-0.1,0.8-0.1,1.3v8.2h-2.7V305.4L747.9,305.4z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 108 KiB |
BIN
addons/cetmix_tower/static/description/commands_1.png
Normal file
|
After Width: | Height: | Size: 285 KiB |
BIN
addons/cetmix_tower/static/description/commands_2.png
Normal file
|
After Width: | Height: | Size: 342 KiB |
62
addons/cetmix_tower/static/description/connectivity.svg
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Server" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#039BE5;}
|
||||||
|
.st1{opacity:0.1;fill:#FFFFFF;enable-background:new ;}
|
||||||
|
.st2{opacity:5.000000e-02;enable-background:new ;}
|
||||||
|
.st3{fill:#303C42;}
|
||||||
|
.st4{opacity:0.1;enable-background:new ;}
|
||||||
|
.st5{opacity:0.2;enable-background:new ;}
|
||||||
|
.st6{fill:#69B342;}
|
||||||
|
.st7{fill:#C6CDD1;}
|
||||||
|
.st8{fill:url(#SVGID_1_);}
|
||||||
|
.st9{fill:url(#SVGID_2_);}
|
||||||
|
.st10{fill:url(#SVGID_3_);}
|
||||||
|
</style>
|
||||||
|
<circle class="st0" cx="11.1" cy="11.1" r="9.2"/>
|
||||||
|
<path class="st1" d="M11.1,2c-3.8,0-7,2.3-8.4,5.5h16.8C18.1,4.2,14.9,2,11.1,2z"/>
|
||||||
|
<path class="st2" d="M2.7,14.8c1.4,3.2,4.6,5.5,8.4,5.5s7-2.3,8.4-5.5H2.7z"/>
|
||||||
|
<path class="st1" d="M7.5,11.1C7.5,6.1,8.6,2,11.1,2C6.1,2,2,6.1,2,11.1s4.1,9.2,9.2,9.2C8.6,20.3,7.5,16.2,7.5,11.1z"/>
|
||||||
|
<path class="st2" d="M11.1,2c2.5,0,3.7,4.1,3.7,9.2s-1.1,9.2-3.7,9.2c5.1,0,9.2-4.1,9.2-9.2S16.2,2,11.1,2z"/>
|
||||||
|
<path class="st3" d="M6.5,16.2h14.7V18H6.5V16.2z"/>
|
||||||
|
<path class="st4" d="M6.5,16.2h14.7V18H6.5V16.2z"/>
|
||||||
|
<path class="st3" d="M21.2,16.6H6.5c-0.5,0-0.9-0.4-0.9-0.9V13c0-0.5,0.4-0.9,0.9-0.9h14.7c0.5,0,0.9,0.4,0.9,0.9v2.7
|
||||||
|
C22.1,16.2,21.7,16.6,21.2,16.6z"/>
|
||||||
|
<path class="st1" d="M21.2,12.1H6.5c-0.5,0-0.9,0.4-0.9,0.9v0.2c0-0.5,0.4-0.9,0.9-0.9h14.7c0.5,0,0.9,0.4,0.9,0.9V13
|
||||||
|
C22.1,12.5,21.7,12.1,21.2,12.1z"/>
|
||||||
|
<path class="st5" d="M21.2,16.4H6.5c-0.5,0-0.9-0.4-0.9-0.9v0.2c0,0.5,0.4,0.9,0.9,0.9h14.7c0.5,0,0.9-0.4,0.9-0.9v-0.2
|
||||||
|
C22.1,16,21.7,16.4,21.2,16.4z"/>
|
||||||
|
<path class="st3" d="M21.2,22.1H6.5c-0.5,0-0.9-0.4-0.9-0.9v-2.7c0-0.5,0.4-0.9,0.9-0.9h14.7c0.5,0,0.9,0.4,0.9,0.9v2.7
|
||||||
|
C22.1,21.7,21.7,22.1,21.2,22.1z"/>
|
||||||
|
<circle class="st6" cx="19.8" cy="19.8" r="0.5"/>
|
||||||
|
<circle class="st6" cx="18" cy="19.8" r="0.5"/>
|
||||||
|
<circle class="st6" cx="16.2" cy="19.8" r="0.5"/>
|
||||||
|
<circle class="st7" cx="7.9" cy="19.8" r="0.9"/>
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="7.336" y1="6.7378" x2="8.3616" y2="5.7121" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#000000;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<circle class="st8" cx="7.9" cy="19.8" r="0.9"/>
|
||||||
|
<circle class="st6" cx="19.8" cy="14.3" r="0.5"/>
|
||||||
|
<circle class="st6" cx="18" cy="14.3" r="0.5"/>
|
||||||
|
<circle class="st6" cx="16.2" cy="14.3" r="0.5"/>
|
||||||
|
<path class="st1" d="M21.2,17.6H6.5c-0.5,0-0.9,0.4-0.9,0.9v0.2c0-0.5,0.4-0.9,0.9-0.9h14.7c0.5,0,0.9,0.4,0.9,0.9v-0.2
|
||||||
|
C22.1,18,21.7,17.6,21.2,17.6z"/>
|
||||||
|
<path class="st5" d="M21.2,21.9H6.5c-0.5,0-0.9-0.4-0.9-0.9v0.2c0,0.5,0.4,0.9,0.9,0.9h14.7c0.5,0,0.9-0.4,0.9-0.9V21
|
||||||
|
C22.1,21.5,21.7,21.9,21.2,21.9z"/>
|
||||||
|
<circle class="st7" cx="7.9" cy="14.3" r="0.9"/>
|
||||||
|
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="7.336" y1="12.2373" x2="8.3616" y2="11.2116" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#000000;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<circle class="st9" cx="7.9" cy="14.3" r="0.9"/>
|
||||||
|
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="2.2175" y1="17.461" x2="23.5011" y2="7.5369" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.2"/>
|
||||||
|
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st10" d="M22.1,15.7V13c0-0.5-0.4-0.9-0.9-0.9h-1c0-0.3,0-0.6,0-0.9c0-5.1-4.1-9.2-9.2-9.2S2,6.1,2,11.1
|
||||||
|
c0,3,1.4,5.6,3.7,7.3l0,0v2.7c0,0.5,0.4,0.9,0.9,0.9h14.7c0.5,0,0.9-0.4,0.9-0.9v-2.7c0-0.5-0.4-0.9-0.9-0.9v-0.9
|
||||||
|
C21.7,16.6,22.1,16.2,22.1,15.7z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
30
addons/cetmix_tower/static/description/cx_logo.svg
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 200 200" style="enable-background:new 0 0 200 200;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#29A1DC;}
|
||||||
|
.st1{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<circle class="st0" cx="100.5" cy="99.8" r="92.2"/>
|
||||||
|
<path class="st1" d="M113.4,127.1c0.6,0.3,0.7,1,0.2,1.5c-3.1,2.8-6.6,5.2-10.3,7.1c-6.6,3.2-13.8,4.8-21,4.7
|
||||||
|
c-7.2-0.3-14.3-2.2-20.4-5.7s-11.3-8.5-15.1-14.4c-3.7-5.9-5.8-12.6-6.4-19.4c-0.4-6.8,0.8-13.7,3.5-19.8c1.5-3,3.1-5.9,5.2-8.5
|
||||||
|
c2.1-2.6,4.5-5,7.1-7c5.2-4,11.4-6.7,17.8-7.8c-6.3,1.5-12.2,4.6-16.9,8.9c-2.4,2.1-4.5,4.5-6.2,7.1c-1.8,2.6-3.1,5.5-4.3,8.4
|
||||||
|
c-2.1,5.9-2.7,12.2-1.9,18.3c0.9,6.1,3.3,11.8,6.8,16.7c3.6,4.8,8.4,8.7,13.7,11.3c5.3,2.5,11.3,3.7,17,3.4c5.8-0.3,11.4-2,16.3-4.9
|
||||||
|
c1.9-1.2,3.8-2.5,5.4-4c0.3-0.3,0.7-0.3,1-0.1L113.4,127.1z"/>
|
||||||
|
<path class="st1" d="M131.5,143.1c0.4,0.4,0.3,1.1-0.1,1.4c-0.2,0.2-0.4,0.4-0.6,0.6c-2.6,2.3-5.4,4.4-8.3,6.3c-2.9,1.9-6,3.5-9.2,5
|
||||||
|
c-3.2,1.4-6.4,2.6-9.8,3.5c-13.3,3.6-27.7,2.9-40.4-1.9c-12.8-4.8-23.8-13.5-31.2-24.7c-7.4-11.1-11-24.4-10.6-37.6
|
||||||
|
c0.4-13.1,5.2-25.9,13.2-36.1c7.9-10.2,19-17.7,31.3-21.2c12.2-3.5,25.4-3.2,37.2,1c5.9,2.1,11.4,5.1,16.3,8.8s9.2,8.2,12.7,13.3
|
||||||
|
c-3.8-4.8-8.3-9-13.4-12.4c-5-3.4-10.6-6-16.4-7.7c-11.6-3.4-24.1-2.9-35.2,1c-11.2,3.9-20.9,11.4-27.5,21
|
||||||
|
c-6.6,9.5-10.1,21-9.8,32.4c0.3,11.4,4.2,22.6,10.9,31.5c6.7,8.9,16.3,15.5,26.7,18.7c10.5,3.3,21.8,3.1,32-0.3
|
||||||
|
c8.1-2.7,15.4-7.4,21.2-13.6c0.4-0.4,1.1-0.5,1.5-0.1c0.6,0.6,1.3,1.3,1.8,1.9C123.8,134,129.5,140.8,131.5,143.1z"/>
|
||||||
|
<path class="st1" d="M171.3,74.2l-35.9,43.7l2,4.1l4.1,8.6c0.7,1.5,0.4,3.3-0.8,4.5l-2.9,2.7c-0.2,0.2-0.4,0.2-0.7,0.2
|
||||||
|
c-0.1,0-0.2-0.1-0.3-0.2c0,0-0.1,0-0.1-0.1l-7.9-9.4c-0.8-0.9-1.6-1.8-2.5-2.6c-0.7-0.6-1.3-1.2-2.1-1.8c-1.4-1.1-2.9-2.2-4.6-3
|
||||||
|
c-0.3-0.2-0.5-0.3-0.8-0.4l-9.3-4.8l-1.7-0.9c-0.3-0.2-0.5-0.6-0.3-1l2-3.4c0.9-1.5,2.6-2.2,4.2-1.8l13.8,3.1l14-18.1L112.9,81
|
||||||
|
c-0.4-0.2-0.5-0.7-0.3-1l4.5-5.6c1.8-2.3,4.8-3.4,7.7-3l30.3,5.1l7.5-9.6c1.7-2.2,3.9-3.9,6.4-5l3.5-1.5c0.5-0.2,1-0.2,1.4-0.1
|
||||||
|
c0.3,0.1,0.5,0.2,0.7,0.3l0,0c0.5,0.5,0.9,1.2,0.7,2l-0.6,3.7C174.4,69.2,173.1,71.9,171.3,74.2z"/>
|
||||||
|
<g>
|
||||||
|
<path class="st1" d="M175.6,119.1l-4.5,5.6c-0.2,0.2-0.5,0.3-0.7,0.2c-0.1,0-0.3-0.1-0.4-0.3l-17.3-23.3l13.5-16.1l10.6,25.8
|
||||||
|
C177.9,113.7,177.5,116.8,175.6,119.1z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
63
addons/cetmix_tower/static/description/development.svg
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Server" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#D9DBDC;}
|
||||||
|
.st1{opacity:0.1;fill:#010101;enable-background:new ;}
|
||||||
|
.st2{opacity:0.2;fill:#FFFFFF;enable-background:new ;}
|
||||||
|
.st3{fill:#818181;}
|
||||||
|
.st4{fill:url(#SVGID_1_);}
|
||||||
|
.st5{fill:url(#SVGID_2_);}
|
||||||
|
.st6{fill:#333333;}
|
||||||
|
.st7{opacity:5.000000e-02;fill:#FFFFFF;enable-background:new ;}
|
||||||
|
.st8{fill:url(#SVGID_3_);}
|
||||||
|
.st9{fill:url(#SVGID_4_);}
|
||||||
|
.st10{fill:#00425A;}
|
||||||
|
.st11{fill:#27A0DA;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M1.1,14.7v1.8c0,1,0.8,1.8,1.8,1.8h6.4c0,0.7-0.2,1.9-0.6,2.5c-0.2,0.2-0.5,0.3-0.7,0.3s-0.5,0.2-0.5,0.5
|
||||||
|
S7.6,22,7.9,22h8.2c0.3,0,0.5-0.2,0.5-0.5s-0.2-0.5-0.5-0.5c-0.3,0-0.5-0.1-0.7-0.3c-0.5-0.5-0.6-1.7-0.6-2.5h6.4
|
||||||
|
c1,0,1.8-0.8,1.8-1.8v-1.8H1.1z"/>
|
||||||
|
<path class="st1" d="M21.1,18.2H2.9c-1,0-1.8-0.8-1.8-1.8v0.2c0,1,0.8,1.8,1.8,1.8h18.2c1,0,1.8-0.8,1.8-1.8v-0.3
|
||||||
|
C22.9,17.3,22.1,18.2,21.1,18.2z"/>
|
||||||
|
<path class="st2" d="M7.9,21.3h8.2c0.2,0,0.4,0.2,0.5,0.4v-0.1c0-0.3-0.2-0.5-0.5-0.5H7.9c-0.3-0.1-0.5,0.1-0.5,0.4v0.1
|
||||||
|
C7.6,21.4,7.7,21.3,7.9,21.3z"/>
|
||||||
|
<path class="st3" d="M12,17.7c-0.4,0-0.6-0.4-0.6-0.7c0-0.4,0.3-0.6,0.6-0.6s0.6,0.4,0.6,0.7C12.6,17.3,12.4,17.6,12,17.7z"/>
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="11.3652" y1="-732.7223" x2="12.6001" y2="-733.298" gradientTransform="matrix(1 0 0 -1 0 -716)">
|
||||||
|
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.2"/>
|
||||||
|
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st4" d="M12,17.7c-0.4,0-0.6-0.4-0.6-0.7c0-0.4,0.3-0.6,0.6-0.6s0.6,0.4,0.6,0.7C12.6,17.3,12.4,17.6,12,17.7z"/>
|
||||||
|
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="10.8905" y1="-733.225" x2="14.6633" y2="-736.9988" gradientTransform="matrix(1 0 0 -1 0 -716)">
|
||||||
|
<stop offset="0" style="stop-color:#010101;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#010101;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st5" d="M15.4,20.8c-0.5-0.5-0.6-1.7-0.6-2.5H9.3l2.7,2.7h4.1C15.8,21.1,15.5,21,15.4,20.8z"/>
|
||||||
|
<path class="st6" d="M22.9,3.8c0-1-0.8-1.8-1.8-1.8H2.9c-1,0-1.8,0.8-1.8,1.8v11.8h21.8V3.8z"/>
|
||||||
|
<path class="st7" d="M2.9,2.3h18.2c1,0,1.8,0.8,1.8,1.8V3.8c0-1-0.8-1.8-1.8-1.8H2.9c-1,0-1.8,0.8-1.8,1.8v0.3
|
||||||
|
C1.1,3,1.9,2.3,2.9,2.3z"/>
|
||||||
|
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="0.2378" y1="-721.2634" x2="23.3135" y2="-732.0244" gradientTransform="matrix(1 0 0 -1 0 -716)">
|
||||||
|
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.2"/>
|
||||||
|
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st8" d="M21.1,2H2.9c-1,0-1.8,0.8-1.8,1.8v12.7c0,1,0.8,1.8,1.8,1.8h6.4c0,0.7-0.2,1.9-0.6,2.5
|
||||||
|
c-0.2,0.2-0.5,0.3-0.7,0.3s-0.5,0.2-0.5,0.5S7.6,22,7.9,22h8.2c0.3,0,0.5-0.2,0.5-0.5s-0.2-0.5-0.5-0.5c-0.3,0-0.5-0.1-0.7-0.3
|
||||||
|
c-0.5-0.5-0.6-1.7-0.6-2.5h6.4c1,0,1.8-0.8,1.8-1.8V3.8C22.9,2.8,22.1,2,21.1,2z"/>
|
||||||
|
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="1.7228" y1="746.1478" x2="22.4117" y2="755.7944" gradientTransform="matrix(1 0 0 1 0 -742)">
|
||||||
|
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.2"/>
|
||||||
|
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st9" d="M22,3.7c0-0.5-0.4-0.8-0.7-0.8H2.7C2.4,2.9,2,3.3,2,3.7v11h20V3.7z"/>
|
||||||
|
<path class="st10" d="M6.7,13.8c-0.3,0-0.5-0.1-0.6-0.3c-0.4-0.4-0.4-1,0-1.4l3.1-3.1L6.1,5.9c-0.4-0.4-0.4-1,0-1.4
|
||||||
|
c0.4-0.4,1-0.4,1.4,0l3.8,3.8c0.4,0.4,0.4,1,0,1.4l-3.8,3.8C7.3,13.7,7,13.8,6.7,13.8z M17.3,13.8h-4.7c-0.5,0-0.9-0.5-0.9-0.9
|
||||||
|
s0.5-0.9,0.9-0.9h4.7c0.5,0,0.9,0.5,0.9,0.9S17.7,13.8,17.3,13.8z"/>
|
||||||
|
<g>
|
||||||
|
<path class="st11" d="M6.7,13.3c-0.3,0-0.5-0.1-0.6-0.3c-0.4-0.4-0.4-1,0-1.4l3.1-3.1L6.1,5.4c-0.4-0.4-0.4-1,0-1.4
|
||||||
|
c0.4-0.4,1-0.4,1.4,0l3.8,3.8c0.4,0.4,0.4,1,0,1.4l-3.8,3.8C7.3,13.2,7,13.3,6.7,13.3z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="st11" d="M17.3,13.3h-4.7c-0.5,0-0.9-0.5-0.9-0.9c0-0.5,0.5-0.9,0.9-0.9h4.7c0.5,0,0.9,0.5,0.9,0.9
|
||||||
|
S17.7,13.3,17.3,13.3z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
63
addons/cetmix_tower/static/description/files.svg
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Server" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#303C42;}
|
||||||
|
.st1{opacity:0.1;enable-background:new ;}
|
||||||
|
.st2{opacity:0.2;enable-background:new ;}
|
||||||
|
.st3{opacity:0.1;fill:#FFFFFF;enable-background:new ;}
|
||||||
|
.st4{fill:#428EB3;}
|
||||||
|
.st5{fill:#C6CDD1;}
|
||||||
|
.st6{fill:url(#SVGID_1_);}
|
||||||
|
.st7{fill:url(#SVGID_2_);}
|
||||||
|
.st8{fill:#29A1DC;}
|
||||||
|
.st9{opacity:0.2;fill:#FFFFFF;enable-background:new ;}
|
||||||
|
.st10{fill:url(#SVGID_3_);}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M2,7.5h18.2v2.7H2V7.5z"/>
|
||||||
|
<path class="st1" d="M2,7.5h18.2v2.7H2V7.5z"/>
|
||||||
|
<path class="st0" d="M1.1,3.8v3.6C1.1,8,1.5,8.4,2,8.4h18.2c0.5,0,0.9-0.4,0.9-0.9V3.8c0-0.5-0.4-0.9-0.9-0.9H2
|
||||||
|
C1.5,2.9,1.1,3.3,1.1,3.8z"/>
|
||||||
|
<path class="st2" d="M20.2,8.1H2c-0.5,0-0.9-0.4-0.9-0.9v0.2C1.1,8,1.5,8.4,2,8.4h18.2c0.5,0,0.9-0.4,0.9-0.9V7.2
|
||||||
|
C21.1,7.7,20.7,8.1,20.2,8.1z"/>
|
||||||
|
<path class="st3" d="M20.2,2.9H2c-0.5,0-0.9,0.4-0.9,0.9v0.2c0-0.5,0.4-0.9,0.9-0.9h18.2c0.5,0,0.9,0.4,0.9,0.9V3.8
|
||||||
|
C21.1,3.3,20.7,2.9,20.2,2.9z"/>
|
||||||
|
<circle class="st4" cx="17.9" cy="5.6" r="0.5"/>
|
||||||
|
<circle class="st4" cx="16.1" cy="5.6" r="0.5"/>
|
||||||
|
<circle class="st4" cx="14.3" cy="5.6" r="0.5"/>
|
||||||
|
<circle class="st5" cx="4.7" cy="5.6" r="0.9"/>
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="4.1628" y1="20.9292" x2="5.1788" y2="19.9133" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#000000;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<circle class="st6" cx="4.7" cy="5.6" r="0.9"/>
|
||||||
|
<path class="st0" d="M1.1,10.2v3.6c0,0.5,0.4,0.9,0.9,0.9h18.2c0.5,0,0.9-0.4,0.9-0.9v-3.6c0-0.5-0.4-0.9-0.9-0.9H2
|
||||||
|
C1.5,9.3,1.1,9.7,1.1,10.2z"/>
|
||||||
|
<path class="st2" d="M20.2,14.5H2c-0.5,0-0.9-0.4-0.9-0.9v0.2c0,0.5,0.4,0.9,0.9,0.9h18.2c0.5,0,0.9-0.4,0.9-0.9v-0.2
|
||||||
|
C21.1,14.1,20.7,14.5,20.2,14.5z"/>
|
||||||
|
<path class="st3" d="M20.2,9.3H2c-0.5,0-0.9,0.4-0.9,0.9v0.2c0-0.5,0.4-0.9,0.9-0.9h18.2c0.5,0,0.9,0.4,0.9,0.9v-0.2
|
||||||
|
C21.1,9.7,20.7,9.3,20.2,9.3z"/>
|
||||||
|
<circle class="st4" cx="17.9" cy="12" r="0.5"/>
|
||||||
|
<circle class="st4" cx="16.1" cy="12" r="0.5"/>
|
||||||
|
<circle class="st4" cx="14.3" cy="12" r="0.5"/>
|
||||||
|
<circle class="st5" cx="4.7" cy="12" r="0.9"/>
|
||||||
|
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="4.1628" y1="14.5738" x2="5.1788" y2="13.5578" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#000000;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<circle class="st7" cx="4.7" cy="12" r="0.9"/>
|
||||||
|
<path class="st8" d="M22.4,13.8h-4.4l-0.9-0.9h-3.8c-0.3,0-0.5,0.2-0.5,0.5v7.3c0,0.3,0.2,0.5,0.5,0.5h9.1c0.3,0,0.5-0.2,0.5-0.5
|
||||||
|
v-6.4C22.9,14,22.7,13.8,22.4,13.8z"/>
|
||||||
|
<path class="st9" d="M22.4,13.8h-4.4l-0.9-0.9h-3.8c-0.3,0-0.5,0.2-0.5,0.5v0.2c0-0.3,0.2-0.5,0.5-0.5h3.8l0.9,0.9h4.4
|
||||||
|
c0.3,0,0.5,0.2,0.5,0.5v-0.2C22.9,14,22.7,13.8,22.4,13.8z"/>
|
||||||
|
<path class="st1" d="M22.4,20.9h-9.1c-0.3,0-0.5-0.2-0.5-0.5v0.2c0,0.3,0.2,0.5,0.5,0.5h9.1c0.3,0,0.5-0.2,0.5-0.5v-0.2
|
||||||
|
C22.9,20.6,22.7,20.9,22.4,20.9z"/>
|
||||||
|
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="0.7043" y1="21.5139" x2="25.1518" y2="10.1132" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.2"/>
|
||||||
|
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st10" d="M22.4,13.8h-1.4v-3.6c0-0.5-0.4-0.9-0.9-0.9V8.4c0.5,0,0.9-0.4,0.9-0.9V3.8c0-0.5-0.4-0.9-0.9-0.9H2
|
||||||
|
c-0.5,0-0.9,0.4-0.9,0.9v3.6C1.1,8,1.5,8.4,2,8.4v0.9c-0.5,0-0.9,0.4-0.9,0.9v3.6c0,0.5,0.4,0.9,0.9,0.9h10.9v5.9
|
||||||
|
c0,0.3,0.2,0.5,0.5,0.5h9.1c0.3,0,0.5-0.2,0.5-0.5v-6.4C22.9,14,22.7,13.8,22.4,13.8z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
BIN
addons/cetmix_tower/static/description/files_1.png
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
addons/cetmix_tower/static/description/files_2.png
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
addons/cetmix_tower/static/description/files_3.png
Normal file
|
After Width: | Height: | Size: 281 KiB |
380
addons/cetmix_tower/static/description/flight-plan.svg
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Server" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:none;}
|
||||||
|
.st1{fill:#173E51;}
|
||||||
|
.st2{fill:#51BCF0;}
|
||||||
|
.st3{fill:#29A1DC;}
|
||||||
|
.st4{fill:#1381B7;}
|
||||||
|
.st5{fill:#D1D6D8;}
|
||||||
|
.st6{fill:#C0C2C3;}
|
||||||
|
.st7{fill:url(#SVGID_24_);}
|
||||||
|
.st8{fill:#CECECE;}
|
||||||
|
.st9{fill:#727272;}
|
||||||
|
.st10{fill:url(#SVGID_25_);}
|
||||||
|
.st11{fill:url(#SVGID_26_);}
|
||||||
|
.st12{fill:url(#SVGID_27_);}
|
||||||
|
.st13{fill:url(#SVGID_28_);}
|
||||||
|
.st14{fill:url(#SVGID_29_);}
|
||||||
|
.st15{fill:url(#SVGID_30_);}
|
||||||
|
.st16{fill:url(#SVGID_31_);}
|
||||||
|
.st17{fill:url(#SVGID_32_);}
|
||||||
|
.st18{fill:url(#SVGID_33_);}
|
||||||
|
.st19{fill:url(#SVGID_34_);}
|
||||||
|
.st20{fill:url(#SVGID_35_);}
|
||||||
|
.st21{fill:url(#SVGID_36_);}
|
||||||
|
.st22{fill:url(#SVGID_37_);}
|
||||||
|
.st23{fill:url(#SVGID_38_);}
|
||||||
|
.st24{fill:url(#SVGID_39_);}
|
||||||
|
.st25{fill:url(#SVGID_40_);}
|
||||||
|
.st26{fill:url(#SVGID_41_);}
|
||||||
|
.st27{fill:url(#SVGID_42_);}
|
||||||
|
.st28{fill:url(#SVGID_43_);}
|
||||||
|
.st29{fill:url(#SVGID_44_);}
|
||||||
|
.st30{fill:url(#SVGID_45_);}
|
||||||
|
.st31{fill:url(#SVGID_46_);}
|
||||||
|
</style>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_1_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_10_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st2" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_11_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_12_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st2" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_13_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_14_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st2" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_15_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_16_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_17_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_18_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_19_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_2_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st3" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_20_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st3" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_21_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st4" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_22_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st5" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_23_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st2" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_3_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st4" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_4_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_5_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st5" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_6_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st6" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_7_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="37.5" height="40.5" patternUnits="userSpaceOnUse" id="SVGID_8_" viewBox="0 -40.5 37.5 40.5" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-40.5" class="st0" width="37.5" height="40.5"/>
|
||||||
|
<path class="st0" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
<path class="st2" d="M0-40.5h37.5V0H0V-40.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern y="24" width="103.5" height="99" patternUnits="userSpaceOnUse" id="SVGID_9_" viewBox="0 -99 103.5 99" style="overflow:visible;">
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<g>
|
||||||
|
<rect y="-99" class="st0" width="103.5" height="99"/>
|
||||||
|
<path class="st0" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
<path class="st1" d="M0-99h103.5V0H0V-99z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</pattern>
|
||||||
|
<pattern id="SVGID_24_" xlink:href="#SVGID_1_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st7" d="M15.9,4.3h-1.6V4c0-0.4-0.3-0.7-0.8-0.7h-1.6c-0.1,0-0.2,0-0.2-0.1c0-0.1,0-0.3-0.1-0.4
|
||||||
|
c-0.2-0.5-0.7-0.9-1.3-0.9C9.9,1.8,9.4,2,9,2.3c-0.3,0.3-0.5,0.6-0.5,1c0,0,0,0,0,0c0,0-0.1,0-0.1,0H6.8C6.4,3.3,6,3.6,6,4v0.3H4.5
|
||||||
|
C3.7,4.3,3,4.9,3,5.8v14.6c0,0.8,0.7,1.5,1.5,1.5h11.4c0.8,0,1.5-0.7,1.5-1.5V5.8C17.4,4.9,16.7,4.3,15.9,4.3z M6.7,4
|
||||||
|
c0,0,0.1-0.1,0.2-0.1h1.6c0.2,0,0.4-0.1,0.6-0.2c0.1-0.1,0.2-0.3,0.2-0.5l0,0c0-0.2,0.1-0.3,0.3-0.5c0.3-0.2,0.6-0.3,0.9-0.3
|
||||||
|
c0.3,0.1,0.7,0.3,0.8,0.6c0,0.1,0,0.1,0,0.2c0,0.2,0.1,0.3,0.2,0.4c0.2,0.2,0.3,0.3,0.6,0.3h1.6c0.1,0,0.2,0.1,0.2,0.1v1.2
|
||||||
|
c0,0-0.1,0.1-0.2,0.1H6.8c-0.1,0-0.1,0-0.2-0.1C6.7,5.2,6.7,4,6.7,4z M16.8,20.4c0,0.5-0.4,0.9-0.9,0.9H4.5c0,0-0.1,0-0.1,0
|
||||||
|
s0,0-0.1,0s-0.1,0-0.1,0c0,0-0.1,0-0.1,0S4,21.2,4,21.1c0,0-0.1-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c0,0,0-0.1-0.1-0.1
|
||||||
|
c0,0-0.1-0.1-0.1-0.1c-0.1-0.1-0.1-0.3-0.1-0.4V5.8c0-0.5,0.4-0.9,0.9-0.9h1.6v0.3c0,0.4,0.3,0.7,0.8,0.7h6.7c0.4,0,0.8-0.3,0.8-0.7
|
||||||
|
V4.9h1.6c0.2,0,0.3,0,0.4,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1c0,0,0,0,0.1,0.1c0.1,0.1,0.1,0.1,0.1,0.2c0,0,0,0.1,0.1,0.1
|
||||||
|
c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1s0,0,0,0.1s0,0.1,0,0.1L16.8,20.4L16.8,20.4z"/>
|
||||||
|
<path class="st8" d="M16.8,5.7C16.8,5.7,16.8,5.7,16.8,5.7c0-0.1,0-0.1,0-0.2c0,0,0-0.1,0-0.1c0,0,0-0.1-0.1-0.1
|
||||||
|
c0-0.1-0.1-0.1-0.1-0.2C16.5,5.1,16.4,5,16.3,5l0,0c-0.1-0.1-0.3-0.1-0.4-0.1h-1.6v0.3c0,0.4-0.3,0.7-0.8,0.7H6.8
|
||||||
|
c-0.4,0-0.8-0.3-0.8-0.7V4.9H4.5C4,4.9,3.6,5.3,3.6,5.8v14.6c0,0.2,0,0.3,0.1,0.4c0,0,0,0.1,0.1,0.1c0,0.1,0,0.1,0.1,0.1
|
||||||
|
c0,0,0.1,0.1,0.1,0.1c0,0,0.1,0.1,0.1,0.1c0,0,0.1,0,0.1,0.1c0.1,0,0.1,0,0.1,0.1c0,0,0.1,0,0.1,0s0,0,0.1,0s0.1,0,0.1,0h11.4
|
||||||
|
c0.5,0,0.9-0.4,0.9-0.9L16.8,5.7C16.8,5.7,16.8,5.7,16.8,5.7z M4.6,6.9c0-0.2,0.1-0.3,0.3-0.3h10.7c0.2,0,0.3,0.1,0.3,0.3v13.3
|
||||||
|
c0,0.2-0.1,0.3-0.3,0.3H4.8c-0.2,0-0.3-0.1-0.3-0.3L4.6,6.9L4.6,6.9z"/>
|
||||||
|
<path class="st9" d="M16.8,5.8v14.6c0,0.5-0.4,0.9-0.9,0.9H4.5c-0.3,0-0.7-0.2-0.8-0.5c0.1,0.1,0.3,0.1,0.4,0.1h11.4
|
||||||
|
c0.5,0,0.9-0.4,0.9-0.9V5.4c0-0.2,0-0.3-0.1-0.4C16.6,5.1,16.8,5.4,16.8,5.8z"/>
|
||||||
|
<pattern id="SVGID_25_" xlink:href="#SVGID_2_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st10" d="M13.7,4v1.2c0,0-0.1,0.1-0.2,0.1H6.8c-0.1,0-0.1,0-0.2-0.1V4c0,0,0.1-0.1,0.2-0.1h1.6c0.2,0,0.4-0.1,0.6-0.2
|
||||||
|
c0.1-0.1,0.2-0.3,0.2-0.5l0,0c0-0.2,0.1-0.3,0.3-0.5c0.3-0.2,0.6-0.3,0.9-0.3c0.3,0.1,0.7,0.3,0.8,0.6c0,0.1,0,0.1,0,0.2
|
||||||
|
c0,0.2,0.1,0.3,0.2,0.4c0.2,0.2,0.3,0.3,0.6,0.3L13.7,4C13.7,3.9,13.7,4,13.7,4z"/>
|
||||||
|
<pattern id="SVGID_26_" xlink:href="#SVGID_3_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st11" d="M13.7,4v1.2c0,0-0.1,0.1-0.2,0.1H6.8c-0.1,0-0.1,0-0.2-0.1V4.8H13c0.1,0,0.2,0,0.2-0.1V3.9h0.4
|
||||||
|
C13.7,3.9,13.7,4,13.7,4z"/>
|
||||||
|
<pattern id="SVGID_27_" xlink:href="#SVGID_4_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st12" d="M15.5,6.6H4.8c-0.2,0-0.3,0.1-0.3,0.3v13.3c0,0.2,0.1,0.3,0.3,0.3h10.7c0.2,0,0.3-0.1,0.3-0.3V6.9
|
||||||
|
C15.9,6.7,15.7,6.6,15.5,6.6L15.5,6.6z M15.2,19.9H5.2V7.2h10.1V19.9z"/>
|
||||||
|
<pattern id="SVGID_28_" xlink:href="#SVGID_5_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st13" d="M5.2,7.2v12.7h10.1V7.2H5.2z M8.6,18.8c0,0.2-0.1,0.3-0.3,0.3H6.8c-0.2,0-0.3-0.1-0.3-0.3v-1.5
|
||||||
|
c0-0.2,0.1-0.3,0.3-0.3h1.5c0.2,0,0.3,0.1,0.3,0.3V18.8z M8.6,15.8c0,0.2-0.1,0.3-0.3,0.3H6.8c-0.2,0-0.3-0.1-0.3-0.3v-1.5
|
||||||
|
c0-0.2,0.1-0.3,0.3-0.3h1.5c0.2,0,0.3,0.1,0.3,0.3V15.8z M8.6,12.8c0,0.2-0.1,0.3-0.3,0.3H6.8c-0.2,0-0.3-0.1-0.3-0.3v-1.5
|
||||||
|
c0-0.2,0.1-0.3,0.3-0.3h1.5c0.2,0,0.3,0.1,0.3,0.3V12.8z M8.6,9.8c0,0.2-0.1,0.3-0.3,0.3H6.8c-0.2,0-0.3-0.1-0.3-0.3V8.2
|
||||||
|
c0-0.2,0.1-0.3,0.3-0.3h1.5c0.2,0,0.3,0.1,0.3,0.3V9.8z M13.6,18.4H9.5c-0.2,0-0.3-0.1-0.3-0.3c0-0.2,0.1-0.3,0.3-0.3h4.1
|
||||||
|
c0.2,0,0.3,0.1,0.3,0.3C13.9,18.2,13.7,18.4,13.6,18.4z M13.6,15.4H9.5c-0.2,0-0.3-0.1-0.3-0.3s0.1-0.3,0.3-0.3h4.1
|
||||||
|
c0.2,0,0.3,0.1,0.3,0.3S13.7,15.4,13.6,15.4z M13.6,12.3H9.5c-0.2,0-0.3-0.1-0.3-0.3s0.1-0.3,0.3-0.3h4.1c0.2,0,0.3,0.1,0.3,0.3
|
||||||
|
S13.7,12.3,13.6,12.3z M13.6,9.3H9.5C9.3,9.3,9.2,9.2,9.2,9c0-0.2,0.1-0.3,0.3-0.3h4.1c0.2,0,0.3,0.1,0.3,0.3
|
||||||
|
C13.9,9.2,13.7,9.3,13.6,9.3z"/>
|
||||||
|
<pattern id="SVGID_29_" xlink:href="#SVGID_6_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st14" d="M15.2,7.2v12.7H5.2v-0.6h9.5V7.2H15.2z"/>
|
||||||
|
<pattern id="SVGID_30_" xlink:href="#SVGID_7_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st15" d="M8.3,8H6.8C6.7,8,6.5,8.1,6.5,8.3v1.5c0,0.2,0.1,0.3,0.3,0.3h1.5c0.2,0,0.3-0.1,0.3-0.3V8.3
|
||||||
|
C8.6,8.1,8.5,8,8.3,8z M8,9.5H7.1V8.6H8V9.5z"/>
|
||||||
|
<pattern id="SVGID_31_" xlink:href="#SVGID_8_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st16" d="M7.1,8.6H8v0.9H7.1V8.6z"/>
|
||||||
|
<pattern id="SVGID_32_" xlink:href="#SVGID_9_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st17" d="M8.3,11H6.8c-0.2,0-0.3,0.1-0.3,0.3v1.5c0,0.2,0.1,0.3,0.3,0.3h1.5c0.2,0,0.3-0.1,0.3-0.3v-1.5
|
||||||
|
C8.6,11.1,8.5,11,8.3,11z M8,12.5H7.1v-0.9H8V12.5z"/>
|
||||||
|
<pattern id="SVGID_33_" xlink:href="#SVGID_10_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st18" d="M7.1,11.6H8v0.9H7.1V11.6z"/>
|
||||||
|
<pattern id="SVGID_34_" xlink:href="#SVGID_11_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st19" d="M8.3,14H6.8c-0.2,0-0.3,0.1-0.3,0.3v1.5c0,0.2,0.1,0.3,0.3,0.3h1.5c0.2,0,0.3-0.1,0.3-0.3v-1.5
|
||||||
|
C8.6,14.2,8.5,14,8.3,14z M8,15.5H7.1v-0.9H8V15.5z"/>
|
||||||
|
<pattern id="SVGID_35_" xlink:href="#SVGID_12_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st20" d="M7.1,14.6H8v0.9H7.1V14.6z"/>
|
||||||
|
<pattern id="SVGID_36_" xlink:href="#SVGID_13_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st21" d="M8.3,17H6.8c-0.2,0-0.3,0.1-0.3,0.3l0,0v1.5c0,0.2,0.1,0.3,0.3,0.3h1.5c0.2,0,0.3-0.1,0.3-0.3v-1.5
|
||||||
|
C8.6,17.2,8.5,17,8.3,17z M8,18.6H7.1v-0.9H8V18.6z"/>
|
||||||
|
<pattern id="SVGID_37_" xlink:href="#SVGID_14_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st22" d="M7.1,17.7H8v0.9H7.1V17.7z"/>
|
||||||
|
<pattern id="SVGID_38_" xlink:href="#SVGID_15_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st23" d="M13.9,9c0,0.2-0.1,0.3-0.3,0.3H9.5C9.3,9.3,9.2,9.2,9.2,9c0-0.2,0.1-0.3,0.3-0.3h4.1C13.7,8.7,13.9,8.9,13.9,9
|
||||||
|
z"/>
|
||||||
|
<pattern id="SVGID_39_" xlink:href="#SVGID_16_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st24" d="M13.9,12.1c0,0.2-0.1,0.3-0.3,0.3H9.5c-0.2,0-0.3-0.1-0.3-0.3s0.1-0.3,0.3-0.3h4.1
|
||||||
|
C13.7,11.7,13.9,11.9,13.9,12.1z"/>
|
||||||
|
<pattern id="SVGID_40_" xlink:href="#SVGID_17_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st25" d="M13.9,15.1c0,0.2-0.1,0.3-0.3,0.3H9.5c-0.2,0-0.3-0.1-0.3-0.3s0.1-0.3,0.3-0.3h4.1
|
||||||
|
C13.7,14.8,13.9,14.9,13.9,15.1z"/>
|
||||||
|
<pattern id="SVGID_41_" xlink:href="#SVGID_18_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st26" d="M13.9,18.1c0,0.2-0.1,0.3-0.3,0.3H9.5c-0.2,0-0.3-0.1-0.3-0.3c0-0.2,0.1-0.3,0.3-0.3h4.1
|
||||||
|
C13.7,17.8,13.9,17.9,13.9,18.1z"/>
|
||||||
|
<pattern id="SVGID_42_" xlink:href="#SVGID_19_" patternTransform="matrix(1 0 0 1 -636.654 15918.918)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st27" d="M21,19.3C21,19.3,20.9,19.3,21,19.3l0-9.4l0,0v0c0,0,0-0.1,0-0.1l0,0l-1-2.3c-0.1-0.1-0.2-0.2-0.3-0.2
|
||||||
|
s-0.2,0.1-0.3,0.2l-0.9,2.3l0,0c0,0,0,0,0,0.1s0,0,0,0.1l0,0l0,10.7c0,0.7,0.6,1.3,1.3,1.3l0,0h0.1c0.3,0,0.6-0.1,0.9-0.4
|
||||||
|
c0.2-0.2,0.4-0.5,0.4-0.9V19.3C20.9,19.3,20.9,19.3,21,19.3z M19.6,8.4l0.5,1.3h-1L19.6,8.4z M20.3,10.2l0,8.8H19l0-8.8
|
||||||
|
C19,10.3,20.3,10.2,20.3,10.2z M20.2,21.1c-0.1,0.1-0.3,0.2-0.4,0.2h-0.1c-0.2,0-0.3-0.1-0.4-0.2S19,20.8,19,20.7v-1h1.3v1
|
||||||
|
C20.4,20.8,20.3,21,20.2,21.1z"/>
|
||||||
|
<pattern id="SVGID_43_" xlink:href="#SVGID_20_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st28" d="M20.4,19H19l0-8.8h1.3L20.4,19z"/>
|
||||||
|
<pattern id="SVGID_44_" xlink:href="#SVGID_21_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st29" d="M20.4,19H19v-0.4h1.1l0-8.4h0.3L20.4,19z"/>
|
||||||
|
<pattern id="SVGID_45_" xlink:href="#SVGID_22_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st30" d="M20.2,9.7h-1l0.5-1.3L20.2,9.7z"/>
|
||||||
|
<pattern id="SVGID_46_" xlink:href="#SVGID_23_" patternTransform="matrix(1 0 0 1 -585.044 15869.5332)">
|
||||||
|
</pattern>
|
||||||
|
<path class="st31" d="M20.4,20.6c0,0.2-0.1,0.3-0.2,0.4c-0.1,0.1-0.3,0.2-0.4,0.2h-0.1c-0.2,0-0.3-0.1-0.4-0.2
|
||||||
|
c-0.1-0.1-0.2-0.3-0.2-0.4v-1h1.3V20.6z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 18 KiB |
BIN
addons/cetmix_tower/static/description/flight_plan_1.png
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
addons/cetmix_tower/static/description/flight_plan_2.png
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
addons/cetmix_tower/static/description/flight_plan_3.png
Normal file
|
After Width: | Height: | Size: 351 KiB |
55
addons/cetmix_tower/static/description/git.svg
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Server" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#29A1DC;}
|
||||||
|
.st1{clip-path:url(#SVGID_2_);fill:#7DBCE7;}
|
||||||
|
.st2{fill:#010101;}
|
||||||
|
.st3{fill:#F5CCB3;}
|
||||||
|
.st4{fill:#FFFFFF;}
|
||||||
|
.st5{fill:#AF5C51;}
|
||||||
|
.st6{fill:#C4E5D9;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M18.6,19.8c0,1.3-2.9,2.3-6.5,2.3s-6.5-1-6.5-2.3c0-1.3,2.9-2.3,6.5-2.3S18.6,18.5,18.6,19.8z"/>
|
||||||
|
<g>
|
||||||
|
<defs>
|
||||||
|
<path id="SVGID_1_" d="M9.3,23.8c0.1,0.7,0.5,1.1,0.8,1.3h3.7c0.5-0.3,0.9-0.9,0.9-2v-2.8c0,0,0.1-0.7,0.7-0.9c0,0,0.4-0.3,0-0.4
|
||||||
|
c0,0-1.8-0.1-1.8,1.3v2.1c0,0,0.1,0.8-0.3,1.1V21c0,0,0-0.8,0.5-1.2c0,0,0.3-0.5-0.3-0.4c0,0-1.2,0.2-1.3,1.6l0,2.7h-0.3l0-2.7
|
||||||
|
c-0.1-1.4-1.3-1.6-1.3-1.6c-0.6-0.1-0.3,0.4-0.3,0.4c0.4,0.3,0.5,1.2,0.5,1.2v2.7c-0.4-0.3-0.3-1.1-0.3-1.1v-2.1
|
||||||
|
c0-1.4-1.8-1.3-1.8-1.3c-0.4,0.1,0,0.4,0,0.4c0.6,0.2,0.7,0.9,0.7,0.9v2L9.3,23.8L9.3,23.8z"/>
|
||||||
|
</defs>
|
||||||
|
<clipPath id="SVGID_2_">
|
||||||
|
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
|
||||||
|
</clipPath>
|
||||||
|
<path class="st1" d="M18.6,19.8c0,1.3-2.9,2.3-6.5,2.3s-6.5-1-6.5-2.3c0-1.3,2.9-2.3,6.5-2.3S18.6,18.5,18.6,19.8z"/>
|
||||||
|
</g>
|
||||||
|
<path class="st0" d="M4.7,13.1l-0.2,0.6c0,0,0,0.2,0.2,0.3c0.2,0,0.2-0.2,0.2-0.3L4.7,13.1L4.7,13.1z"/>
|
||||||
|
<path class="st2" d="M23.5,10.4L23.5,10.4c-1.9-0.5-3.8-0.5-5-0.4c0.2-0.7,0.3-1.5,0.3-2.4c0-1.3-0.5-2.3-1.3-3.1
|
||||||
|
C17.6,4,17.8,3,17.3,1.8c0,0-0.9-0.3-2.9,1.1c-0.8-0.2-1.6-0.3-2.5-0.3c-0.9,0-1.8,0.1-2.7,0.4c-2.1-1.4-3-1.1-3-1.1
|
||||||
|
C5.7,3.3,6,4.4,6.1,4.6C5.4,5.4,5,6.4,5,7.6c0,0.9,0.1,1.7,0.4,2.4c-1.2,0-3.1,0-4.9,0.3l0,0.1C2.3,10,4.2,10,5.4,10
|
||||||
|
c0.1,0.1,0.1,0.3,0.2,0.4c-1.2,0-3.2,0.2-5.1,0.7l0,0.1c1.9-0.5,3.9-0.7,5.1-0.7c0.7,1.3,2.1,2.1,4.5,2.4c-0.4,0.2-0.7,0.6-0.8,1.3
|
||||||
|
c-0.5,0.2-2,0.8-2.9-0.8c0,0-0.5-0.9-1.5-1c0,0-0.9,0-0.1,0.6c0,0,0.6,0.3,1.1,1.4c0,0,0.6,1.9,3.3,1.3v1.9c0,0-0.1,0.7-0.7,0.9
|
||||||
|
c0,0-0.4,0.3,0,0.4c0,0,1.8,0.1,1.8-1.3v-2.1c0,0-0.1-0.8,0.3-1.1V18c0,0,0,0.8-0.5,1.2c0,0-0.3,0.5,0.3,0.4c0,0,1.2-0.2,1.3-1.6
|
||||||
|
l0-3.5h0.3l0,3.5c0.1,1.4,1.3,1.6,1.3,1.6c0.6,0.1,0.3-0.4,0.3-0.4c-0.4-0.3-0.5-1.2-0.5-1.2v-3.5c0.4,0.3,0.3,1.1,0.3,1.1v2.1
|
||||||
|
c0,1.4,1.8,1.3,1.8,1.3c0.4-0.1,0-0.4,0-0.4c-0.6-0.2-0.7-0.9-0.7-0.9v-2.8c0-1.1-0.5-1.7-0.9-2c2.6-0.3,3.9-1.1,4.4-2.4
|
||||||
|
c1.1,0,3.2,0.2,5.2,0.7l0-0.1c-2-0.6-4-0.7-5.2-0.7c0.1-0.1,0.1-0.3,0.1-0.4C19.7,10,21.6,10,23.5,10.4L23.5,10.4z"/>
|
||||||
|
<path class="st3" d="M16.2,7.5c0.6,0.5,0.9,1.1,0.9,1.8c0,3.1-2.3,3.2-5.2,3.2S6.8,12,6.8,9.3c0-0.7,0.3-1.3,0.9-1.8
|
||||||
|
c0.9-0.8,2.5-0.4,4.3-0.4S15.3,6.6,16.2,7.5L16.2,7.5z"/>
|
||||||
|
<path class="st4" d="M10.2,9.4c0,0.9-0.5,1.5-1.1,1.5s-1.1-0.7-1.1-1.5c0-0.9,0.5-1.5,1.1-1.5C9.7,7.9,10.2,8.6,10.2,9.4z"/>
|
||||||
|
<path class="st5" d="M9.9,9.5c0,0.6-0.3,1-0.7,1c-0.4,0-0.7-0.5-0.7-1c0-0.6,0.3-1,0.7-1C9.6,8.4,9.9,8.9,9.9,9.5z"/>
|
||||||
|
<path class="st4" d="M16,9.4c0,0.9-0.5,1.5-1.1,1.5c-0.6,0-1.1-0.7-1.1-1.5c0-0.9,0.5-1.5,1.1-1.5C15.5,7.9,16,8.6,16,9.4z"/>
|
||||||
|
<path class="st5" d="M15.6,9.5c0,0.6-0.3,1-0.7,1c-0.4,0-0.7-0.5-0.7-1c0-0.6,0.3-1,0.7-1C15.3,8.4,15.6,8.9,15.6,9.5z M12.2,10.8
|
||||||
|
c0,0.1-0.1,0.3-0.3,0.3c-0.1,0-0.3-0.1-0.3-0.3c0-0.2,0.1-0.3,0.3-0.3C12.1,10.5,12.2,10.7,12.2,10.8z M11.3,11.5c0,0,0-0.1,0.1-0.1
|
||||||
|
c0,0,0.1,0,0.1,0.1c0.1,0.2,0.3,0.3,0.5,0.3c0.2,0,0.4-0.1,0.5-0.3c0,0,0.1-0.1,0.1-0.1c0,0,0.1,0.1,0.1,0.1
|
||||||
|
c-0.1,0.3-0.3,0.4-0.6,0.4C11.7,11.9,11.4,11.8,11.3,11.5L11.3,11.5z"/>
|
||||||
|
<path class="st6" d="M5.4,12.7c0,0.1-0.1,0.1-0.2,0.1c-0.1,0-0.2-0.1-0.2-0.1s0.1-0.1,0.2-0.1C5.3,12.6,5.4,12.6,5.4,12.7z M5.9,13
|
||||||
|
c0,0.1-0.1,0.1-0.2,0.1c-0.1,0-0.2-0.1-0.2-0.1s0.1-0.1,0.2-0.1C5.8,12.9,5.9,12.9,5.9,13z M6.2,13.4c0,0.1-0.1,0.1-0.2,0.1
|
||||||
|
c-0.1,0-0.2-0.1-0.2-0.1c0-0.1,0.1-0.1,0.2-0.1C6.1,13.3,6.2,13.3,6.2,13.4z M6.5,13.8c0,0.1-0.1,0.1-0.2,0.1
|
||||||
|
c-0.1,0-0.2-0.1-0.2-0.1c0-0.1,0.1-0.1,0.2-0.1C6.4,13.7,6.5,13.7,6.5,13.8z M6.8,14.2c0,0.1-0.1,0.1-0.2,0.1
|
||||||
|
c-0.1,0-0.2-0.1-0.2-0.1c0-0.1,0.1-0.1,0.2-0.1C6.7,14.1,6.8,14.1,6.8,14.2z M7.2,14.6c0,0.1-0.1,0.1-0.2,0.1
|
||||||
|
c-0.1,0-0.2-0.1-0.2-0.1c0-0.1,0.1-0.1,0.2-0.1C7.2,14.4,7.2,14.5,7.2,14.6z M7.8,14.8c0,0.1-0.1,0.1-0.2,0.1
|
||||||
|
c-0.1,0-0.2-0.1-0.2-0.1c0-0.1,0.1-0.1,0.2-0.1C7.8,14.7,7.8,14.7,7.8,14.8z M8.5,14.8c0,0.1-0.1,0.1-0.2,0.1
|
||||||
|
c-0.1,0-0.2-0.1-0.2-0.1c0-0.1,0.1-0.1,0.2-0.1C8.4,14.7,8.5,14.7,8.5,14.8z M9.1,14.7c0,0.1-0.1,0.1-0.2,0.1
|
||||||
|
c-0.1,0-0.2-0.1-0.2-0.1c0-0.1,0.1-0.1,0.2-0.1C9,14.6,9.1,14.6,9.1,14.7z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.4 KiB |
BIN
addons/cetmix_tower/static/description/git_1.png
Normal file
|
After Width: | Height: | Size: 318 KiB |
BIN
addons/cetmix_tower/static/description/git_2.png
Normal file
|
After Width: | Height: | Size: 460 KiB |
BIN
addons/cetmix_tower/static/description/icon.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
690
addons/cetmix_tower/static/description/index.html
Normal file
@@ -0,0 +1,690 @@
|
|||||||
|
<section class="oe_container custom_style description-odoo-font">
|
||||||
|
<div id="wrap" class="oe_structure oe_empty">
|
||||||
|
<section class="s_text_block o_colored_level pb120 pt168 oe_img_bg o_bg_img_center" data-snippet="s_text_block" data-name="Text" style="background-image: url('tower_website.svg'); position: relative;">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h2 style="color: #0D3045; font-weight: 500; font-size: 48px" data-bs-original-title="" title="" aria-describedby="tooltip483440">
|
||||||
|
<strong style="font-weight: 800; color: #29A1DC; vertical-align: text-bottom;">| </strong> What is Cetmix Tower?
|
||||||
|
<br/>
|
||||||
|
</h2>
|
||||||
|
<p style="font-size: 18px; font-weight:300">
|
||||||
|
<b>Cetmix Tower</b> is an <b>open-source</b> DevOps framework built on Odoo. <br/><br/>
|
||||||
|
It empowers you to deploy, manage, and automate applications <b>directly from Odoo</b> whether it's Odoo itself, WordPress, ERPNext or Magento.<br/><br/>
|
||||||
|
Unlike traditional hosting platforms, Cetmix Tower is <b>not tied to any specific technology</b>. You can use it with Docker, Kubernetes, a bare operating system or whatever your infrastructure requires.<br/><br/>
|
||||||
|
Fully <b>self-hosted</b> and deeply integrated with the Odoo ecosystem, it gives you complete control over your servers and applications. <br/><br/>
|
||||||
|
<b>Cetmix Tower</b> is distributed under the <b>AGPL-3 license</b>, ensuring transparency and freedom.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<img src="cetmix_tower_server_kanban.png" alt="" class="img img-fluid o_we_custom_image" data-original-id="1256" data-original-src="cetmix_tower_server_kanban.png" data-mimetype="image/png" style="width: 100% !important;" loading="eager"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="s_text_image pb32 o_colored_level o_cc o_cc1" data-snippet="s_image_text" style="color: #343A40;" data-name="Custom Image - Text (2)" data-bs-original-title="" title="" aria-describedby="tooltip882751">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 table-responsive">
|
||||||
|
<h2 style="color: #0D3045; font-weight: 500; font-size: 48px"> <strong style="font-weight: 800; color: #29A1DC; vertical-align: text-bottom;">| </strong>Key Differences</h2>
|
||||||
|
<!-- Bootstrap 5 CSS -->
|
||||||
|
<table class="table table-striped align-middle border">
|
||||||
|
<thead>
|
||||||
|
<tr class="align-middle">
|
||||||
|
<th class="text-center"> </th>
|
||||||
|
<th class="text-center"><b>Cetmix Tower</b></th>
|
||||||
|
<th class="text-center">Odoo.sh</th>
|
||||||
|
<th class="text-center">Other Odoo Hosting</th>
|
||||||
|
<th class="text-center">Generic DevOps Tools</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-start">Can Deploy Odoo Enterprise</td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-start">Can Deploy Odoo Community</td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-start">Can Deploy Other Software besides Odoo</td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-start">Runs on Your Own Server</td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center">Some*</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-start">Native Odoo App</td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-start">Export/Import & Share Configurations</td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center">Some*</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-start">Is Open Source</td>
|
||||||
|
<td class="text-center"><i class="fa fa-check text-success"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center"><i class="fa fa-close text-danger"></i></td>
|
||||||
|
<td class="text-center">Some*</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="small text-muted">* depends on the tool</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h2 style="color: #0D3045; font-weight: 500; font-size: 48px"> <strong style="font-weight: 800; color: #29A1DC; vertical-align: text-bottom;">| </strong>Why Cetmix Tower?</h2>
|
||||||
|
<ul class="card-text text-800" style="font-size: 18px; font-weight:300; color: #3d4856 !important; text-align: left;">
|
||||||
|
<li><strong>Easy to use for non-technical users:</strong> Deploy a server or run a multi-step scenario with a single click in the UI.</li>
|
||||||
|
<li><strong>Power and flexibility for technical users:</strong> Leverage powerful features to build complex workflows and automate server and application management tasks.</li>
|
||||||
|
<li><strong>Not limited to a single technology:</strong> Run any software manageable via shell commands or API, whether you're using Docker, Kubernetes, or direct OS commands.</li>
|
||||||
|
<li><strong>Odoo Integration:</strong> Take advantage of the Odoo ecosystem for server management tasks. Integrate with Sales, Accounting, Subscriptions, Helpdesk, or any other Odoo module.</li>
|
||||||
|
<li><strong>Not limited to Odoo:</strong> While optimized for Odoo, Cetmix Tower can manage virtually any software.</li>
|
||||||
|
<li><strong>Extensibility:</strong> Develop your own Odoo modules based on Cetmix Tower to implement custom features.</li>
|
||||||
|
<li><strong>Self-Hosting:</strong> Deploy Cetmix Tower on your own infrastructure for complete control over your server and application management.</li>
|
||||||
|
<li><strong>Open Source:</strong> Cetmix Tower is distributed under the AGPL-3 license, ensuring transparency and freedom.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container pt64">
|
||||||
|
<h2 class="pb32" style="color: #0D3045; font-weight: 500; font-size: 48px"><strong style="font-weight: 800; color: #29A1DC; vertical-align: text-bottom;">| </strong> Core Features</h2>
|
||||||
|
<div class="container">
|
||||||
|
<div class="pt32" style="margin-left: 40px; background-color:#0D3045; border-radius: 40px; padding: 24px;">
|
||||||
|
<p class="card-text text-800" style="font-size: 18px; font-weight:300; color: #d7dcdf !important; text-align: left;"><strong style="font-weight: 800; color: #29A1DC; vertical-align: text-bottom;">| </strong>Note</p>
|
||||||
|
<p class="card-text text-800 pb32" style="font-size: 18px; font-weight:300; color: #d7dcdf !important; text-align: left;">Cetmix Tower is designed with usability and simplicity in mind, though some features might require a foundational understanding of server management principles</p>
|
||||||
|
<h4 style="font-size: 24px; color: #ffffff; font-weight: 200;">
|
||||||
|
For documentation please check <strong style="font-size: 32px; font-weight: 800; color: #29A1DC;"> | </strong> <a href="https://tower.cetmix.com" style="color: #29A1DC;"> tower.cetmix.com</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container pt64">
|
||||||
|
<h3 style="color: #0D3045; font-weight: 500; font-size: 48px"><img src="self-host.svg" style="height: 48px; margin-right: 10px; vertical-align: text-top;"/>Server Management</h3>
|
||||||
|
<ul class="card-text text-800" style="font-size: 18px; font-weight:300; color: #3d4856 !important; text-align: left;">
|
||||||
|
<li>Variable-based flexible configuration</li>
|
||||||
|
<li>Create Servers using pre-defined Server Templates</li>
|
||||||
|
</ul>
|
||||||
|
<section
|
||||||
|
class="s_image_gallery pt24 pb24 o_colored_level o_spc-small o_masonry o_cc o_cc1"
|
||||||
|
data-vcss="001"
|
||||||
|
data-columns="2"
|
||||||
|
data-snippet="s_images_wall"
|
||||||
|
data-name="Images Wall"
|
||||||
|
style="overflow: hidden"
|
||||||
|
>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row s_nb_column_fixed">
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="server_1.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1218"
|
||||||
|
data-original-src="server_1.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
loading="eager"
|
||||||
|
title="Server Management"
|
||||||
|
aria-describedby="tooltip65859"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="server_template_form.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1242"
|
||||||
|
data-original-src="server_template_form.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
data-bs-original-title=""
|
||||||
|
title="Server Management"
|
||||||
|
aria-describedby="tooltip990969"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="server_form_configuration.png"
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
loading="eager"
|
||||||
|
data-original-id="1225"
|
||||||
|
data-original-src="server_form_configuration.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
title="Server Management"
|
||||||
|
aria-describedby="tooltip997009"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="container pt64">
|
||||||
|
<h3 style="color: #0D3045; font-weight: 500; font-size: 48px"><img src="connectivity.svg" style="height: 48px; margin-right: 10px; vertical-align: text-top;"/>Connectivity</h3>
|
||||||
|
<ul class="card-text text-800" style="font-size: 18px; font-weight:300; color: #3d4856 !important; text-align: left;">
|
||||||
|
<li>Password and key-based authentication for outgoing SSH connections</li>
|
||||||
|
<li>Built-in support of the Python requests library for outgoing API calls</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="container pt64">
|
||||||
|
<h3 style="color: #0D3045; font-weight: 500; font-size: 48px"><img src="development.svg" style="height: 48px; margin-right: 10px; vertical-align: text-top;"/>Commands</h3>
|
||||||
|
<ul class="card-text text-800" style="font-size: 18px; font-weight:300; color: #3d4856 !important; text-align: left;">
|
||||||
|
<li>Execute SSH Commands on remote servers</li>
|
||||||
|
<li>Run Python Commands on the Tower Odoo server</li>
|
||||||
|
<li>Run Flight Plan from command</li>
|
||||||
|
<li>Render commands using Variables</li>
|
||||||
|
<li>Secrets/Keys for private data storage</li>
|
||||||
|
</ul>
|
||||||
|
<section
|
||||||
|
class="s_image_gallery pt24 pb24 o_colored_level o_spc-small o_masonry o_cc o_cc1"
|
||||||
|
data-vcss="001"
|
||||||
|
data-columns="2"
|
||||||
|
data-snippet="s_images_wall"
|
||||||
|
data-name="Images Wall"
|
||||||
|
style="overflow: hidden"
|
||||||
|
>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row s_nb_column_fixed">
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="commands_1.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1218"
|
||||||
|
data-original-src="commands_1.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
loading="eager"
|
||||||
|
title="Commands"
|
||||||
|
aria-describedby="tooltip65859"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="commands_2.png"
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
loading="eager"
|
||||||
|
data-original-id="1225"
|
||||||
|
data-original-src="commands_2.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
title="Commands"
|
||||||
|
aria-describedby="tooltip997009"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="container pt64">
|
||||||
|
<h3 style="color: #0D3045; font-weight: 500; font-size: 48px"><img src="flight-plan.svg" style="height: 48px; margin-right: 10px; vertical-align: text-top;"/>Flight Plans</h3>
|
||||||
|
<ul class="card-text text-800" style="font-size: 18px; font-weight:300; color: #3d4856 !important; text-align: left;">
|
||||||
|
<li>Execute multiple Commands in a row</li>
|
||||||
|
<li>Condition-based flow:</li>
|
||||||
|
<ul>
|
||||||
|
<li>Based on conditions using Python syntax</li>
|
||||||
|
<li>Based on the previous command exit code</li>
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
|
<section
|
||||||
|
class="s_image_gallery pt24 pb24 o_colored_level o_spc-small o_masonry o_cc o_cc1"
|
||||||
|
data-vcss="001"
|
||||||
|
data-columns="2"
|
||||||
|
data-snippet="s_images_wall"
|
||||||
|
data-name="Images Wall"
|
||||||
|
style="overflow: hidden"
|
||||||
|
>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row s_nb_column_fixed">
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="flight_plan_1.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1218"
|
||||||
|
data-original-src="flight_plan_1.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
loading="eager"
|
||||||
|
title="Flight Plans"
|
||||||
|
aria-describedby="tooltip65859"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="flight_plan_3.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1242"
|
||||||
|
data-original-src="flight_plan_3.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
title="Flight Plans"
|
||||||
|
aria-describedby="tooltip990969"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="flight_plan_2.png"
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
loading="eager"
|
||||||
|
data-original-id="1225"
|
||||||
|
data-original-src="flight_plan_2.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
title="Flight Plans"
|
||||||
|
aria-describedby="tooltip997009"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="container pt64">
|
||||||
|
<h3 style="color: #0D3045; font-weight: 500; font-size: 48px"><img src="files.svg" style="height: 48px; margin-right: 10px; vertical-align: text-top;"/>Files</h3>
|
||||||
|
<ul class="card-text text-800" style="font-size: 18px; font-weight:300; color: #3d4856 !important; text-align: left;">
|
||||||
|
<li>Download Files from a remote server using SFTP</li>
|
||||||
|
<li>Upload Files to a remote server using SFTP</li>
|
||||||
|
<li>Support for <code>text</code> and <code>binary</code> file formats</li>
|
||||||
|
<li>Manage Files using pre-defined File Templates</li>
|
||||||
|
</ul>
|
||||||
|
<section
|
||||||
|
class="s_image_gallery pt24 pb24 o_colored_level o_spc-small o_masonry o_cc o_cc1"
|
||||||
|
data-vcss="001"
|
||||||
|
data-columns="2"
|
||||||
|
data-snippet="s_images_wall"
|
||||||
|
data-name="Images Wall"
|
||||||
|
style="overflow: hidden"
|
||||||
|
>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row s_nb_column_fixed">
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="files_1.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1218"
|
||||||
|
data-original-src="files_1.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
loading="eager"
|
||||||
|
title="Files"
|
||||||
|
aria-describedby="tooltip65859"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="files_3.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1242"
|
||||||
|
data-original-src="files_3.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
title="Files"
|
||||||
|
aria-describedby="tooltip990969"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="files_2.png"
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
loading="eager"
|
||||||
|
data-original-id="1225"
|
||||||
|
data-original-src="files_2.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
title="Files"
|
||||||
|
aria-describedby="tooltip997009"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="container pt64">
|
||||||
|
<h3 style="color: #0D3045; font-weight: 500; font-size: 48px"><img src="server-logs.svg" style="height: 48px; margin-right: 10px; vertical-align: text-top;"/>Server Logs</h3>
|
||||||
|
<ul class="card-text text-800" style="font-size: 18px; font-weight:300; color: #3d4856 !important; text-align: left;">
|
||||||
|
<li>Fetch Logs from a remote server using Commands</li>
|
||||||
|
<li>Fetch Logs from a remote server using Files</li>
|
||||||
|
</ul>
|
||||||
|
<section
|
||||||
|
class="s_image_gallery pt24 pb24 o_colored_level o_spc-small o_masonry o_cc o_cc1"
|
||||||
|
data-vcss="001"
|
||||||
|
data-columns="2"
|
||||||
|
data-snippet="s_images_wall"
|
||||||
|
data-name="Images Wall"
|
||||||
|
style="overflow: hidden"
|
||||||
|
>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row s_nb_column_fixed">
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="logs_1.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1218"
|
||||||
|
data-original-src="logs_1.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
loading="eager"
|
||||||
|
title="Server Logs"
|
||||||
|
aria-describedby="tooltip65859"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="logs_2.png"
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
loading="eager"
|
||||||
|
data-original-id="1225"
|
||||||
|
data-original-src="logs_2.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
title="Server Logs"
|
||||||
|
aria-describedby="tooltip997009"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container pt64 pb64">
|
||||||
|
<h3 style="color: #0D3045; font-weight: 500; font-size: 48px"><img src="tools.svg" style="height: 48px; margin-right: 10px; vertical-align: text-top;"/>Import/Export Data in YAML Format</h3>
|
||||||
|
<ul class="card-text text-800" style="font-size: 18px; font-weight:300; color: #3d4856 !important; text-align: left;">
|
||||||
|
<li>Share and manage data easily using YAML format</li>
|
||||||
|
</ul>
|
||||||
|
<section
|
||||||
|
class="s_image_gallery pt24 pb24 o_colored_level o_spc-small o_masonry o_cc o_cc1"
|
||||||
|
data-vcss="001"
|
||||||
|
data-columns="2"
|
||||||
|
data-snippet="s_images_wall"
|
||||||
|
data-name="Images Wall"
|
||||||
|
style="overflow: hidden"
|
||||||
|
>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row s_nb_column_fixed">
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="yaml_1.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1218"
|
||||||
|
data-original-src="yaml_1.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
loading="eager"
|
||||||
|
title="Import/Export Data in YAML Format"
|
||||||
|
aria-describedby="tooltip65859"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="yaml_4.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1218"
|
||||||
|
data-original-src="yaml_4.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
loading="eager"
|
||||||
|
title="Import/Export Data in YAML Format"
|
||||||
|
aria-describedby="tooltip65859"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="yaml_3.png"
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
loading="eager"
|
||||||
|
data-original-src="yaml_3.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
title="Import/Export Data in YAML Format"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="yaml_5.png"
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
loading="eager"
|
||||||
|
data-original-src="yaml_5.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
title="Import/Export Data in YAML Format"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src="yaml_2.png"
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
loading="eager"
|
||||||
|
data-original-src="yaml_2.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
title="Import/Export Data in YAML Format"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="container pt64 pb64">
|
||||||
|
<h3 style="color: #0D3045; font-weight: 500; font-size: 48px"><img src="git.svg" style="height: 48px; margin-right: 10px; vertical-align: text-top;"/>Manage Git Projects</h3>
|
||||||
|
<ul class="card-text text-800" style="font-size: 18px; font-weight:300; color: #3d4856 !important; text-align: left;">
|
||||||
|
<li>
|
||||||
|
Manage Git projects directly from Odoo
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
User-friendly interface for controlling git repositories linked to your servers
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<section
|
||||||
|
class="s_image_gallery pt24 pb24 o_colored_level o_spc-small o_masonry o_cc o_cc1"
|
||||||
|
data-vcss="001"
|
||||||
|
data-columns="2"
|
||||||
|
data-snippet="s_images_wall"
|
||||||
|
data-name="Images Wall"
|
||||||
|
style="overflow: hidden"
|
||||||
|
>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row s_nb_column_fixed">
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="git_1.png"
|
||||||
|
alt=""
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
data-original-id="1218"
|
||||||
|
data-original-src="git_1.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
data-resize-width="1920"
|
||||||
|
loading="eager"
|
||||||
|
title="Git Projects"
|
||||||
|
aria-describedby="tooltip65859"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="o_masonry_col o_snippet_not_selectable col-lg-6">
|
||||||
|
<img
|
||||||
|
src="git_2.png"
|
||||||
|
class="img-fluid d-block rounded shadow mb16 o_visible img o_we_custom_image"
|
||||||
|
loading="eager"
|
||||||
|
data-original-src="git_2.png"
|
||||||
|
data-mimetype="image/png"
|
||||||
|
title="Git Projects"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_text_image o_colored_level pt64 pb64 o_cc o_cc5 " data-snippet="s_image_text" style="background-color: #0D3045 !important; background-image: none; position: relative; border-top-right-radius: 40px; border-top-left-radius: 40px;" data-name="Custom Image - Text" data-bs-original-title="" title="" aria-describedby="tooltip522920" data-oe-shape-data="{"shape":"web_editor/Origins/16","colors":{"c3":"#0D2945"},"flip":[]}">
|
||||||
|
<div class="container" style="padding: 30px;">
|
||||||
|
<div class="col-lg-6" style="text-align: right; margin-left: auto;">
|
||||||
|
<img src="cetmix.svg" alt="" class="img img-fluid o_we_custom_image" data-original-id="1256" data-original-src="cetmix.svg" data-mimetype="image/svg+xml" data-bs-original-title="" title="" aria-describedby="tooltip359107" style="width: 50% !important;" loading="eager"/>
|
||||||
|
</div>
|
||||||
|
<h3 class="pt64" style="color: #ffffff; font-weight: 500; font-size: 48px"><strong style="font-weight: 800; color: #29A1DC; vertical-align: text-bottom;">| </strong> Warning</h3>
|
||||||
|
<p class="card-text text-800" style="font-size: 18px; font-weight:300; color: #ffffff !important; text-align: left;">
|
||||||
|
The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement.
|
||||||
|
In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_title o_colored_level o_cc o_cc1" style="position: relative; background-color: #0D3045 !important">
|
||||||
|
<div style="background-color: #3d4856 !important; border-top-right-radius: 40px; border-top-left-radius: 40px; padding: 20px;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_features o_colored_level pb64 o_cc o_cc1" data-snippet="s_features" data-name="Features" style="position: relative; background-color: #3d4856 !important" data-oe-shape-data="">
|
||||||
|
<div class="container mx-3">
|
||||||
|
<h2 style="font-size: 48px; font-weight:500; color: #d7dcdf;">License</h2>
|
||||||
|
<p class="card-text text-800" style="font-size: 18px; font-weight:300; color: #d7dcdf !important; text-align: left;">Cetmix Tower is distributed under the AGPL-3 license. In case you want to use this software in projects that are not compatible with AGPL-3 license, you should contact us directly.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_title o_colored_level o_cc o_cc1" style="position: relative; background-color: #3d4856">
|
||||||
|
<div style="background-color: #d7dcdf !important; border-top-right-radius: 40px; border-top-left-radius: 40px; padding: 20px;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_text_block o_colored_level pt32 pb64 o_cc o_cc1" data-snippet="s_text_block" data-name="Text" style="position: relative; background-color: #d7dcdf !important; color: #3d4856;">
|
||||||
|
<div class="container mx-3">
|
||||||
|
<h2 style="font-size: 48px; font-weight:500; color: #3d4856;">Copyright</h2>
|
||||||
|
<p class="card-text text-800" style="font-size: 18px; font-weight:300; color: #3d4856 !important; text-align: left;">Cetmix Tower is a trademark of Cetmix. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_title o_colored_level o_cc o_cc1" style="position: relative; background-color: #d7dcdf">
|
||||||
|
<div style="background-color: #3d4856 !important; border-top-right-radius: 40px; border-top-left-radius: 40px; padding: 20px;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_text_block o_colored_level pt32 pb64 o_cc o_cc1" data-snippet="s_text_block" data-name="Text" style="position: relative; background-color: #3d4856 !important; color: #3d4856;">
|
||||||
|
<div class="container mx-3">
|
||||||
|
<h2 style="font-size: 48px; font-weight:500; color: #d7dcdf;">Support</h2>
|
||||||
|
|
||||||
|
<ul class="card-text text-800" style="font-size: 18px; font-weight:300; color: #d7dcdf !important; text-align: left;">
|
||||||
|
<li>This project is open source. All issues and feature requests should be reported in the GitHub repository</li>
|
||||||
|
<li>Your contribution is welcome. Please refer to the CONTRIBUTING.md file for more details.</li>
|
||||||
|
<li>Dedicated support is available on request. Contact us for details at cetmix.com</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
<section class="s_title o_colored_level o_cc o_cc1" style="position: relative; background-color: #3d4856">
|
||||||
|
<div style="background-color: #f4f4f4 !important; border-top-right-radius: 40px; border-top-left-radius: 40px; padding: 20px;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="cetmix" class="s_text_image o_colored_level pt64 pb64 o_cc o_cc5" data-snippet="s_image_text" style="background-color: #f4f4f4 !important; background-image: none; position: relative;" data-name="Custom Image - Text" data-bs-original-title="" title="" aria-describedby="tooltip522920">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="pt16 pb16 o_colored_level col-lg-4" style="position: relative;">
|
||||||
|
<img src="cx_logo.svg" alt="" class="img img-fluid mx-auto o_we_custom_image" style="position: relative;" data-original-id="1229" data-original-src="pdf.svg" data-mimetype="image/svg+xml" data-resize-width="undefined" loading="eager"/>
|
||||||
|
</div>
|
||||||
|
<div class="pb16 o_colored_level col-lg-8" >
|
||||||
|
<h1 style="text-align: left; color:#0D3045; font-size: 48px; font-weight:500;">
|
||||||
|
Cetmix is not just another IT company
|
||||||
|
</h1>
|
||||||
|
<h5 class="pt32" style="text-align: left; color:#3d4856; font-size: 22px; font-weight:300">
|
||||||
|
We know how the business works. Our experts have management
|
||||||
|
experience in heavy machinery, energy sector, logistics,
|
||||||
|
accounting, public services and many other industries _
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
We are the people of business
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_title o_colored_level o_cc o_cc1" style="position: relative; background-color: #f4f4f4">
|
||||||
|
<div style="background-color: #29A1DC !important; border-top-right-radius: 40px; border-top-left-radius: 40px; padding: 20px;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_call_to_action pt48 pb64 o_colored_level o_cc o_cc5" data-snippet="s_call_to_action" data-name="Call to Action" style="background-color: #29A1DC; color: #ffffff;">
|
||||||
|
<div class="container" style="margin-left: 100px">
|
||||||
|
<div class="row o_header_white_border_left">
|
||||||
|
<div class="col-lg-6 pb16 o_colored_level">
|
||||||
|
<h3 style="text-align: left; color:#ffffff; font-size: 48px; font-weight:500;">Solutions for your business</h3>
|
||||||
|
<h5 class="pt16" style="text-align: left; color:#3d4856; font-size: 22px; font-weight:300">Choose an existing one or we can develop a custom one for you</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col pt32 o_colored_level">
|
||||||
|
<p style="text-align: center;" data-bs-original-title="" title="" aria-describedby="tooltip742456">
|
||||||
|
<a class="mb-2 btn btn-lg shadow" href="https://apps.odoo.com/apps/modules/browse?author=Cetmix" data-bs-original-title="" title="" style="color: #ffffff; border-color: #ffffff; border-radius:20px; font-size: 22px; font-weight:300">Check our Apps</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_text_block o_colored_level o_cc o_cc5" data-snippet="s_text_block" data-name="Text" style="position: relative; background-color: #29A1DC; color: #ffffff; ">
|
||||||
|
<div class="s_allow_columns container-fluid">
|
||||||
|
<div class="row d-flex mx-3 pb64">
|
||||||
|
<div class="o_colored_level col-lg-4" >
|
||||||
|
<h2 style="text-align: center;">
|
||||||
|
<span style="color: #ffffff; font-size: 36px; font-weight:500;">>9 <br/> years</span>
|
||||||
|
</h2>
|
||||||
|
<h5 style="text-align: center; color:#3d4856; font-size: 22px; font-weight:300">of Odoo experience
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="o_colored_level col-lg-4" >
|
||||||
|
<h2 style="text-align: center;">
|
||||||
|
<span style="color: #ffffff; font-size: 36px; font-weight:500;">> 15 000 <br/> downloads</span>
|
||||||
|
</h2>
|
||||||
|
<h5 style="text-align: center; color:#3d4856; font-size: 22px; font-weight:300">of our apps from Odoo App Store
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="o_colored_level col-lg-3" >
|
||||||
|
<h2 style="text-align: center;">
|
||||||
|
<span style="color: #ffffff; font-size: 36px; font-weight:500;">>100 <br/> clients</span>
|
||||||
|
</h2>
|
||||||
|
<h5 style="text-align: center; color:#3d4856; font-size: 22px; font-weight:300">
|
||||||
|
are happy with our services
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_title o_colored_level o_cc o_cc1" style="position: relative; background-color: #0D3045">
|
||||||
|
<div style="background-color: #29A1DC !important; border-bottom-right-radius: 40px; border-bottom-left-radius: 40px; padding: 20px;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="s_text_block pt64 pb48 o_colored_level" data-snippet="s_text_block" data-name="Text" style="background-color: #0D3045 !important; border-bottom-right-radius: 40px; border-bottom-left-radius: 40px;" data-bs-original-title="" title="" aria-describedby="tooltip180671">
|
||||||
|
<div class="s_allow_columns container mx-5" data-bs-original-title="" title="" aria-describedby="tooltip154809">
|
||||||
|
<h2 style="text-align: left; color:#ffffff; font-size: 48px; font-weight:500;">
|
||||||
|
Need support, customization<br/>or interested in collaboration ?
|
||||||
|
</h2>
|
||||||
|
<h5 class="pb32" style="font-size: 24px; left; color: #ffffff;">
|
||||||
|
<strong style="font-weight: 800; color: #29A1DC; vertical-align: text-bottom;">| </strong>cetmix.com
|
||||||
|
<br/>
|
||||||
|
</h5>
|
||||||
|
<p style="color: #ffffff;">
|
||||||
|
<span class="fa fa-linkedin-square fa-3x pt16 mr-3" data-bs-original-title="" title="" aria-describedby="tooltip359279" style="color: #29A1DC !important;"></span>
|
||||||
|
https://www.linkedin.com/company/cetmix/
|
||||||
|
</p>
|
||||||
|
<p style="color: #ffffff;">
|
||||||
|
<span class="fa fa-github-square fa-3x pt16 mr-3" data-bs-original-title="" title="" aria-describedby="tooltip359279" style="color: #29A1DC !important;"></span>
|
||||||
|
https://github.com/cetmix/
|
||||||
|
</p>
|
||||||
|
<p style="color: #ffffff;">
|
||||||
|
<span class="fa fa-facebook-square fa-3x pt16 mr-3" data-bs-original-title="" title="" aria-describedby="tooltip359279" style="color: #29A1DC !important;"></span>
|
||||||
|
https://www.facebook.com//cetmixteam
|
||||||
|
</p>
|
||||||
|
<p style="color: #ffffff;">
|
||||||
|
<span class="fa fa-twitter-square fa-3x pt16 mr-3" data-bs-original-title="" title="" aria-describedby="tooltip359279" style="color: #29A1DC !important;"></span>
|
||||||
|
https://twitter.com/cetmix_team
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
BIN
addons/cetmix_tower/static/description/logs_1.png
Normal file
|
After Width: | Height: | Size: 291 KiB |
BIN
addons/cetmix_tower/static/description/logs_2.png
Normal file
|
After Width: | Height: | Size: 716 KiB |
1
addons/cetmix_tower/static/description/self-host.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="Server"><path fill="#303c42" d="M2 7h20v3H2z" class="color303c42 svgShape"></path><path d="M2 7h20v3H2z" opacity=".1" fill="#000000" class="color000000 svgShape"></path><path fill="#303c42" d="M2 14h20v3H2z" class="color303c42 svgShape"></path><path d="M2 14h20v3H2z" opacity=".1" fill="#000000" class="color000000 svgShape"></path><path fill="#303c42" d="M1 3v4a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1z" class="color303c42 svgShape"></path><path d="M22 7.75H2a1 1 0 0 1-1-1V7a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-.25a1 1 0 0 1-1 1z" opacity=".2" fill="#000000" class="color000000 svgShape"></path><path fill="#ffffff" d="M22 2H2a1 1 0 0 0-1 1v.25a1 1 0 0 1 1-1h20a1 1 0 0 1 1 1V3a1 1 0 0 0-1-1z" opacity=".1" class="colorffffff svgShape"></path><circle cx="19.5" cy="5" r=".5" fill="#428eb3" class="color69b342 svgShape"></circle><circle cx="17.5" cy="5" r=".5" fill="#428eb3" class="color69b342 svgShape"></circle><circle cx="15.5" cy="5" r=".5" fill="#428eb3" class="color69b342 svgShape"></circle><circle cx="5" cy="5" r="1" fill="#29a1dc" class="colorc6cdd1 svgShape"></circle><linearGradient id="a" x1="4.368" x2="5.487" y1="4.368" y2="5.487" gradientUnits="userSpaceOnUse"><stop offset="0" stop-opacity=".1"></stop><stop offset="1" stop-opacity="0"></stop></linearGradient><circle cx="5" cy="5" r="1" fill="url(#a)"></circle><path fill="#303c42" d="M1 10v4a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1z" class="color303c42 svgShape"></path><path d="M22 14.75H2a1 1 0 0 1-1-1V14a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-.25a1 1 0 0 1-1 1z" opacity=".2" fill="#000000" class="color000000 svgShape"></path><path fill="#ffffff" d="M22 9H2a1 1 0 0 0-1 1v.25a1 1 0 0 1 1-1h20a1 1 0 0 1 1 1V10a1 1 0 0 0-1-1z" opacity=".1" class="colorffffff svgShape"></path><circle cx="19.5" cy="12" r=".5" fill="#428eb3" class="color69b342 svgShape"></circle><circle cx="17.5" cy="12" r=".5" fill="#428eb3" class="color69b342 svgShape"></circle><circle cx="15.5" cy="12" r=".5" fill="#428eb3" class="color69b342 svgShape"></circle><circle cx="5" cy="12" r="1" fill="#29a1dc" class="colorc6cdd1 svgShape"></circle><linearGradient id="b" x1="4.368" x2="5.487" y1="11.368" y2="12.487" gradientUnits="userSpaceOnUse"><stop offset="0" stop-opacity=".1"></stop><stop offset="1" stop-opacity="0"></stop></linearGradient><circle cx="5" cy="12" r="1" fill="url(#b)"></circle><path fill="#303c42" d="M1 17v4a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1z" class="color303c42 svgShape"></path><path d="M22 21.75H2a1 1 0 0 1-1-1V21a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-.25a1 1 0 0 1-1 1z" opacity=".2" fill="#000000" class="color000000 svgShape"></path><path fill="#ffffff" d="M22 16H2a1 1 0 0 0-1 1v.25a1 1 0 0 1 1-1h20a1 1 0 0 1 1 1V17a1 1 0 0 0-1-1z" opacity=".1" class="colorffffff svgShape"></path><circle cx="19.5" cy="19" r=".5" fill="#428eb3" class="color69b342 svgShape"></circle><circle cx="17.5" cy="19" r=".5" fill="#428eb3" class="color69b342 svgShape"></circle><circle cx="15.5" cy="19" r=".5" fill="#428eb3" class="color69b342 svgShape"></circle><circle cx="5" cy="19" r="1" fill="#29a1dc" class="colorc6cdd1 svgShape"></circle><linearGradient id="c" x1="4.368" x2="5.487" y1="18.368" y2="19.487" gradientUnits="userSpaceOnUse"><stop offset="0" stop-opacity=".1"></stop><stop offset="1" stop-opacity="0"></stop></linearGradient><circle cx="5" cy="19" r="1" fill="url(#c)"></circle><linearGradient id="d" x1="-.568" x2="24.568" y1="6.14" y2="17.86" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ffffff" stop-opacity=".2" class="stopColorffffff svgShape"></stop><stop offset="1" stop-color="#ffffff" stop-opacity="0" class="stopColorffffff svgShape"></stop></linearGradient><path fill="url(#d)" d="M23 7V3a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1v1a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1v1a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1v-1a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1V8a1 1 0 0 0 1-1z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 3.9 KiB |
93
addons/cetmix_tower/static/description/server-logs.svg
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Server" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#303C42;}
|
||||||
|
.st1{opacity:0.1;enable-background:new ;}
|
||||||
|
.st2{opacity:0.1;fill:#FFFFFF;enable-background:new ;}
|
||||||
|
.st3{opacity:0.2;enable-background:new ;}
|
||||||
|
.st4{fill:#428EB3;}
|
||||||
|
.st5{fill:#C6CDD1;}
|
||||||
|
.st6{fill:url(#SVGID_1_);}
|
||||||
|
.st7{fill:url(#SVGID_2_);}
|
||||||
|
.st8{fill:#D8DBDC;}
|
||||||
|
.st9{fill:#333333;}
|
||||||
|
.st10{fill:#4D4D4D;}
|
||||||
|
.st11{fill:url(#SVGID_3_);}
|
||||||
|
.st12{opacity:0.2;fill:#FFFFFF;enable-background:new ;}
|
||||||
|
.st13{fill:#29A1DC;}
|
||||||
|
.st14{fill:url(#SVGID_4_);}
|
||||||
|
.st15{fill:url(#SVGID_5_);}
|
||||||
|
.st16{fill:url(#SVGID_6_);}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M2.8,6h14.7v1.8H2.8V6z"/>
|
||||||
|
<path class="st1" d="M2.8,6h14.7v1.8H2.8V6z"/>
|
||||||
|
<path class="st0" d="M17.5,6.5H2.8C2.3,6.5,1.8,6,1.8,5.5V2.8c0-0.5,0.4-0.9,0.9-0.9h14.7c0.5,0,0.9,0.4,0.9,0.9v2.8
|
||||||
|
C18.4,6,18,6.5,17.5,6.5z"/>
|
||||||
|
<path class="st2" d="M17.5,1.9H2.8c-0.5,0-0.9,0.4-0.9,0.9V3c0-0.5,0.4-0.9,0.9-0.9h14.7c0.5,0,0.9,0.4,0.9,0.9V2.8
|
||||||
|
C18.4,2.3,18,1.9,17.5,1.9z"/>
|
||||||
|
<path class="st3" d="M17.5,6.2H2.8c-0.5,0-0.9-0.4-0.9-0.9v0.2c0,0.5,0.4,0.9,0.9,0.9h14.7c0.5,0,0.9-0.4,0.9-0.9V5.3
|
||||||
|
C18.4,5.8,18,6.2,17.5,6.2z"/>
|
||||||
|
<path class="st0" d="M17.5,12H2.8c-0.5,0-0.9-0.4-0.9-0.9V8.3c0-0.5,0.4-0.9,0.9-0.9h14.7c0.5,0,0.9,0.4,0.9,0.9v2.8
|
||||||
|
C18.4,11.6,18,12,17.5,12z"/>
|
||||||
|
<circle class="st4" cx="16.1" cy="9.7" r="0.5"/>
|
||||||
|
<circle class="st4" cx="14.3" cy="9.7" r="0.5"/>
|
||||||
|
<circle class="st4" cx="12.4" cy="9.7" r="0.5"/>
|
||||||
|
<circle class="st5" cx="4.1" cy="9.7" r="0.9"/>
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="3.5643" y1="16.9049" x2="4.5946" y2="15.8746" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#000000;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<circle class="st6" cx="4.1" cy="9.7" r="0.9"/>
|
||||||
|
<circle class="st4" cx="16.1" cy="4.2" r="0.5"/>
|
||||||
|
<circle class="st4" cx="14.3" cy="4.2" r="0.5"/>
|
||||||
|
<circle class="st4" cx="12.4" cy="4.2" r="0.5"/>
|
||||||
|
<path class="st2" d="M17.5,7.4H2.8c-0.5,0-0.9,0.4-0.9,0.9v0.2c0-0.5,0.4-0.9,0.9-0.9h14.7c0.5,0,0.9,0.4,0.9,0.9V8.3
|
||||||
|
C18.4,7.8,18,7.4,17.5,7.4z"/>
|
||||||
|
<path class="st3" d="M17.5,11.7H2.8c-0.5,0-0.9-0.4-0.9-0.9v0.2c0,0.5,0.4,0.9,0.9,0.9h14.7c0.5,0,0.9-0.4,0.9-0.9v-0.2
|
||||||
|
C18.4,11.3,18,11.7,17.5,11.7z"/>
|
||||||
|
<circle class="st5" cx="4.1" cy="4.2" r="0.9"/>
|
||||||
|
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="3.5643" y1="22.4291" x2="4.5946" y2="21.3988" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#000000;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<circle class="st7" cx="4.1" cy="4.2" r="0.9"/>
|
||||||
|
<path class="st8" d="M17,19.3c0-0.3-0.2-0.5-0.5-0.5c-0.3,0-0.5,0.2-0.5,0.5h-1.8c0-0.3-0.2-0.5-0.5-0.5s-0.5,0.2-0.5,0.5
|
||||||
|
c0,1-0.5,1.8-0.9,1.8v0.5H18v-0.5C17.5,21.2,17,20.3,17,19.3z"/>
|
||||||
|
<path class="st9" d="M22.1,10.6c0-0.8-0.6-1.4-1.4-1.4h-11c-0.8,0-1.4,0.6-1.4,1.4V18h13.8V10.6z"/>
|
||||||
|
<path class="st8" d="M8.3,18v0.5c0,0.8,0.6,1.4,1.4,1.4h11c0.8,0,1.4-0.6,1.4-1.4V18H8.3z"/>
|
||||||
|
<path class="st1" d="M20.7,19.6h-11c-0.8,0-1.4-0.6-1.4-1.4v0.2c0,0.8,0.6,1.4,1.4,1.4h11c0.8,0,1.4-0.6,1.4-1.4v-0.2
|
||||||
|
C22.1,19,21.5,19.6,20.7,19.6z"/>
|
||||||
|
<path class="st2" d="M9.7,9.4h11c0.8,0,1.4,0.6,1.4,1.4v-0.2c0-0.8-0.6-1.4-1.4-1.4h-11c-0.8,0-1.4,0.6-1.4,1.4v0.2
|
||||||
|
C8.3,10.1,8.9,9.4,9.7,9.4z"/>
|
||||||
|
<path class="st10" d="M21.2,10.6c0-0.3-0.2-0.5-0.5-0.5h-11c-0.3,0-0.5,0.2-0.5,0.5V17h12V10.6z"/>
|
||||||
|
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="9.0616" y1="15.1862" x2="21.4007" y2="9.4328" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.2"/>
|
||||||
|
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st11" d="M21.2,10.6c0-0.3-0.2-0.5-0.5-0.5h-11c-0.3,0-0.5,0.2-0.5,0.5V17h12V10.6z"/>
|
||||||
|
<path class="st8" d="M18,22.1h-5.5c-0.3,0-0.5-0.2-0.5-0.5c0-0.3,0.2-0.5,0.5-0.5H18c0.3,0,0.5,0.2,0.5,0.5
|
||||||
|
C18.4,21.9,18.2,22.1,18,22.1z"/>
|
||||||
|
<path class="st12" d="M12.4,21.4H18c0.2,0,0.4,0.1,0.4,0.3c0,0,0-0.1,0-0.1c0-0.3-0.2-0.5-0.5-0.5h-5.5c-0.3,0-0.5,0.2-0.5,0.5
|
||||||
|
c0,0,0,0.1,0,0.1C12,21.6,12.2,21.4,12.4,21.4z"/>
|
||||||
|
<circle class="st13" cx="15.2" cy="18.9" r="0.5"/>
|
||||||
|
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="14.7771" y1="7.3105" x2="15.6119" y2="6.9216" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.2"/>
|
||||||
|
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<circle class="st14" cx="15.2" cy="18.9" r="0.5"/>
|
||||||
|
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="14.4175" y1="6.9761" x2="16.8002" y2="4.5933" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#000000;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st15" d="M17.1,19.8h-3.8l1.4,1.4H18C17.6,21.2,17.2,20.6,17.1,19.8z"/>
|
||||||
|
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="0.5921" y1="20.7473" x2="23.4202" y2="10.102" gradientTransform="matrix(1 0 0 -1 0 26)">
|
||||||
|
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.2"/>
|
||||||
|
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st16" d="M20.7,9.2h-2.3V8.3c0-0.5-0.4-0.9-0.9-0.9V6.5c0.5,0,0.9-0.4,0.9-0.9V2.8c0-0.5-0.4-0.9-0.9-0.9H2.8
|
||||||
|
c-0.5,0-0.9,0.4-0.9,0.9v2.8c0,0.5,0.4,0.9,0.9,0.9v0.9c-0.5,0-0.9,0.4-0.9,0.9v2.8c0,0.5,0.4,0.9,0.9,0.9h5.5v6.4
|
||||||
|
c0,0.8,0.6,1.4,1.4,1.4h3.6c-0.1,0.8-0.5,1.4-0.9,1.4c-0.3,0-0.5,0.2-0.5,0.5c0,0.3,0.2,0.5,0.5,0.5H18c0.3,0,0.5-0.2,0.5-0.5
|
||||||
|
c0-0.3-0.2-0.5-0.5-0.5c-0.4,0-0.8-0.6-0.9-1.4h3.6c0.8,0,1.4-0.6,1.4-1.4v-7.8C22.1,9.8,21.5,9.2,20.7,9.2z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.8 KiB |
BIN
addons/cetmix_tower/static/description/server_1.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
addons/cetmix_tower/static/description/server_3.png
Normal file
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 45 KiB |
BIN
addons/cetmix_tower/static/description/server_template_form.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
159
addons/cetmix_tower/static/description/tools.svg
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Server" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#303C42;}
|
||||||
|
.st1{opacity:0.1;enable-background:new ;}
|
||||||
|
.st2{opacity:0.2;enable-background:new ;}
|
||||||
|
.st3{opacity:0.1;fill:#FFFFFF;enable-background:new ;}
|
||||||
|
.st4{fill:#428EB3;}
|
||||||
|
.st5{fill:#C6CDD1;}
|
||||||
|
.st6{fill:url(#SVGID_1_);}
|
||||||
|
.st7{fill:url(#SVGID_2_);}
|
||||||
|
.st8{fill:none;}
|
||||||
|
.st9{fill:url(#SVGID_3_);}
|
||||||
|
.st10{fill:#29A1DC;}
|
||||||
|
.st11{fill:#EFF2F4;}
|
||||||
|
.st12{fill:#ACB5BA;}
|
||||||
|
.st13{fill:#CEDAE0;}
|
||||||
|
.st14{fill:#E6EBEE;}
|
||||||
|
.st15{fill:#3F5C6B;}
|
||||||
|
.st16{fill:#22353F;}
|
||||||
|
.st17{fill:#2B424E;}
|
||||||
|
.st18{fill:#324F5E;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M2,7.5h18.2v2.7H2V7.5z"/>
|
||||||
|
<path class="st1" d="M2,7.5h18.2v2.7H2V7.5z"/>
|
||||||
|
<path class="st0" d="M1.1,3.8v3.6c0,0.6,0.4,1,0.9,1h18.2c0.5,0,0.9-0.4,0.9-0.9V3.8c0-0.5-0.4-0.9-0.9-0.9H2
|
||||||
|
C1.5,2.9,1.1,3.3,1.1,3.8z"/>
|
||||||
|
<path class="st2" d="M20.2,8.1H2c-0.5,0-0.9-0.4-0.9-0.9v0.2c0,0.6,0.4,1,0.9,1h18.2c0.5,0,0.9-0.4,0.9-0.9V7.2
|
||||||
|
C21.1,7.7,20.7,8.1,20.2,8.1z"/>
|
||||||
|
<path class="st3" d="M20.2,2.9H2c-0.5,0-0.9,0.4-0.9,0.9V4c0-0.5,0.4-0.9,0.9-0.9h18.2c0.5,0,0.9,0.4,0.9,0.9V3.8
|
||||||
|
C21.1,3.3,20.7,2.9,20.2,2.9z"/>
|
||||||
|
<circle class="st4" cx="17.9" cy="5.6" r="0.5"/>
|
||||||
|
<circle class="st4" cx="16.1" cy="5.6" r="0.5"/>
|
||||||
|
<circle class="st4" cx="14.3" cy="5.6" r="0.5"/>
|
||||||
|
<circle class="st5" cx="4.7" cy="5.6" r="0.9"/>
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="4.1668" y1="-1953.0668" x2="5.1828" y2="-1954.0828" gradientTransform="matrix(1 0 0 -1 0 -1948)">
|
||||||
|
<stop offset="0" style="stop-color:#000000;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<circle class="st6" cx="4.7" cy="5.6" r="0.9"/>
|
||||||
|
<path class="st0" d="M1.1,10.2v3.6c0,0.5,0.4,0.9,0.9,0.9h18.2c0.5,0,0.9-0.4,0.9-0.9v-3.6c0-0.5-0.4-0.9-0.9-0.9H2
|
||||||
|
C1.5,9.3,1.1,9.7,1.1,10.2z"/>
|
||||||
|
<path class="st2" d="M20.2,14.5H2c-0.5,0-0.9-0.4-0.9-0.9v0.2c0,0.5,0.4,0.9,0.9,0.9h18.2c0.5,0,0.9-0.4,0.9-0.9v-0.2
|
||||||
|
C21.1,14.1,20.7,14.5,20.2,14.5z"/>
|
||||||
|
<path class="st3" d="M20.2,9.3H2c-0.5,0-0.9,0.4-0.9,0.9v0.2c0-0.5,0.4-0.9,0.9-0.9h18.2c0.5,0,0.9,0.4,0.9,0.9v-0.2
|
||||||
|
C21.1,9.7,20.7,9.3,20.2,9.3z"/>
|
||||||
|
<circle class="st4" cx="17.9" cy="12" r="0.5"/>
|
||||||
|
<circle class="st4" cx="16.1" cy="12" r="0.5"/>
|
||||||
|
<circle class="st4" cx="14.3" cy="12" r="0.5"/>
|
||||||
|
<circle class="st5" cx="4.7" cy="12" r="0.9"/>
|
||||||
|
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="4.1445" y1="-1959.4445" x2="5.1605" y2="-1960.4604" gradientTransform="matrix(1 0 0 -1 0 -1948)">
|
||||||
|
<stop offset="0" style="stop-color:#000000;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<circle class="st7" cx="4.7" cy="12" r="0.9"/>
|
||||||
|
<path class="st8" d="M19.1,13c0,0.4,0.3,0.7,0.7,0.8l0,0c0.4,0,0.8-0.3,0.8-0.8c0-0.4-0.3-0.7-0.7-0.8c0,0,0,0-0.1,0
|
||||||
|
C19.5,12.3,19.1,12.6,19.1,13L19.1,13z"/>
|
||||||
|
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="0.6335" y1="1978.4908" x2="25.081" y2="1989.8916" gradientTransform="matrix(1 0 0 1 0 -1974)">
|
||||||
|
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.2"/>
|
||||||
|
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st9" d="M22.4,13.8H21v-3.6c0-0.5-0.4-0.9-0.9-0.9V8.4C20.6,8.4,21,8,21,7.5V3.8c0-0.5-0.4-0.9-0.9-0.9H2
|
||||||
|
C1.5,2.9,1,3.3,1,3.8v3.6c0,0.6,0.4,1,0.9,1v0.9C1.5,9.3,1,9.7,1,10.2v3.6c0,0.5,0.4,0.9,0.9,0.9h10.9v5.9c0,0.3,0.2,0.5,0.5,0.5
|
||||||
|
h9.1c0.3,0,0.5-0.2,0.5-0.5v-6.4C22.9,14,22.7,13.8,22.4,13.8z"/>
|
||||||
|
<path class="st10" d="M22.4,16.2c0,2.7-2.2,4.8-4.8,4.8c-2.7,0-4.8-2.1-4.8-4.8c0-2.7,2.2-4.8,4.8-4.8
|
||||||
|
C20.2,11.3,22.4,13.5,22.4,16.2z"/>
|
||||||
|
<path class="st11" d="M22,12.7l-0.5-0.1c0,0-0.1,0-0.1-0.1c0-0.2-0.1-0.3-0.2-0.5c0,0,0-0.1,0-0.1l0.2-0.4c0,0,0-0.1,0-0.1l-0.3-0.3
|
||||||
|
c0,0-0.1,0-0.1,0l-0.4,0.2c0,0-0.1,0-0.1,0c-0.1-0.1-0.3-0.1-0.4-0.2c0,0-0.1,0-0.1-0.1L20,10.6c0,0-0.1-0.1-0.1-0.1h-0.4
|
||||||
|
c0,0-0.1,0-0.1,0.1l-0.1,0.5c0,0,0,0.1-0.1,0.1c0,0-0.1,0-0.1,0c-0.1,0-0.2,0.1-0.2,0.1c0,0-0.1,0-0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0
|
||||||
|
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l-0.4-0.2c0,0-0.1,0-0.1,0l-0.2,0.2h0l-0.1,0.1h0c0,0,0,0,0,0.1c0,0,0,0.1,0,0.1l0.2,0.4
|
||||||
|
c0,0,0,0,0,0c0,0,0,0.1,0,0.1c-0.1,0.1-0.1,0.3-0.2,0.5c0,0,0,0.1-0.1,0.1l-0.5,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0.1v0.4
|
||||||
|
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0.5,0.1c0,0,0.1,0,0.1,0.1c0,0.2,0.1,0.3,0.2,0.5c0,0,0,0.1,0,0.1l-0.2,0.4
|
||||||
|
c0,0,0,0.1,0,0.1c0,0,0,0,0,0.1h0l0.3,0.3c0,0,0.1,0,0.1,0l0.4-0.2c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0.1
|
||||||
|
c0.1,0,0.2,0.1,0.2,0.1c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0.1l0.1,0.5c0,0,0.1,0.1,0.1,0.1h0.4c0.1,0,0.1,0,0.1-0.1l0.1-0.5
|
||||||
|
c0,0,0-0.1,0.1-0.1c0.2,0,0.3-0.1,0.4-0.2c0,0,0.1,0,0.1,0l0.4,0.2c0,0,0.1,0,0.1,0l0.3-0.3c0,0,0-0.1,0-0.1l-0.2-0.4
|
||||||
|
c0,0,0-0.1,0-0.1c0.1-0.1,0.1-0.3,0.2-0.5c0,0,0,0,0-0.1c0,0,0,0,0,0l0.5-0.1c0,0,0.1-0.1,0.1-0.1v-0.4C22.1,12.8,22.1,12.7,22,12.7
|
||||||
|
z M19.8,14.1c-0.1,0-0.3,0-0.4,0c-0.2,0-0.4-0.1-0.6-0.3c0,0,0,0,0,0c-0.1-0.2-0.2-0.3-0.2-0.5c0-0.1,0-0.3,0-0.4
|
||||||
|
c0-0.5,0.4-0.9,0.8-1c0.2,0,0.3-0.1,0.5,0c0,0,0,0,0,0c0.2,0,0.4,0.1,0.6,0.3c0,0,0,0,0,0c0.1,0.1,0.1,0.1,0.1,0.2l0,0
|
||||||
|
c0.1,0.1,0.1,0.3,0.1,0.4C20.9,13.4,20.4,14,19.8,14.1C19.8,14.1,19.8,14.1,19.8,14.1z"/>
|
||||||
|
<path class="st8" d="M18.9,13c0,0.4,0.3,0.7,0.7,0.8c0,0,0,0,0,0c0.4,0,0.8-0.3,0.8-0.8c0-0.4-0.3-0.7-0.7-0.8c0,0,0,0-0.1,0
|
||||||
|
C19.2,12.3,18.9,12.6,18.9,13L18.9,13z"/>
|
||||||
|
<path class="st12" d="M20.8,13c0-0.6-0.5-1.2-1.1-1.2c0.1,0.1,0.2,0.2,0.1,0.3c0,0.1-0.1,0.1-0.2,0.1c0.4,0,0.7,0.4,0.7,0.8
|
||||||
|
c0,0.4-0.3,0.8-0.8,0.8c0,0,0,0,0,0c-0.1,0-0.2,0.1-0.2,0.2c0,0.1,0.1,0.2,0.2,0.2c0,0,0,0,0.1,0C20.3,14.2,20.8,13.7,20.8,13z"/>
|
||||||
|
<path class="st13" d="M18.9,13.3c0-0.1-0.1-0.2-0.1-0.3c0-0.4,0.3-0.8,0.8-0.8c0,0,0,0,0.1,0c0.1,0,0.2-0.2,0.2-0.3
|
||||||
|
c0-0.1-0.1-0.1-0.1-0.2c0,0-0.1,0-0.1,0c-0.7,0-1.2,0.5-1.2,1.2c0,0.1,0,0.2,0,0.3C18.5,13.3,18.9,13.3,18.9,13.3z"/>
|
||||||
|
<path class="st13" d="M19.7,13.8C19.7,13.8,19.6,13.8,19.7,13.8c-0.5,0-0.8-0.3-0.8-0.8c0,0,0,0,0-0.1h-0.5c0,0,0,0,0,0.1
|
||||||
|
c0,0.7,0.5,1.2,1.2,1.2c0,0,0,0,0.1,0c0.1,0,0.2-0.1,0.2-0.2C19.9,13.9,19.8,13.8,19.7,13.8z"/>
|
||||||
|
<path class="st8" d="M20.5,13c0,0.5-0.4,0.8-0.8,0.9c0,0,0,0,0,0c-0.5,0-0.9-0.4-0.9-0.9c0-0.5,0.3-0.8,0.8-0.8c0,0,0,0,0.1,0
|
||||||
|
C20.1,12.2,20.5,12.6,20.5,13L20.5,13z"/>
|
||||||
|
<path class="st14" d="M19.2,14.8c-0.6-0.4-1-1-1-1.8c0-0.8,0.4-1.4,1-1.8v-0.1c-0.2,0-0.3,0.1-0.5,0.2c0,0-0.1,0-0.1,0l-0.4-0.2
|
||||||
|
c0,0-0.1,0-0.1,0l-0.3,0.3c0,0,0,0.1,0,0.1l0.2,0.4c0,0,0,0.1,0,0.1c-0.1,0.1-0.1,0.3-0.2,0.5c0,0,0,0.1-0.1,0.1l-0.5,0.1
|
||||||
|
c0,0-0.1,0.1-0.1,0.1v0.4c0,0.1,0,0.1,0.1,0.1l0.5,0.1c0,0,0.1,0,0.1,0.1c0,0.2,0.1,0.3,0.2,0.5c0,0,0,0.1,0,0.1l-0.2,0.4
|
||||||
|
c0,0,0,0.1,0,0.1l0.3,0.3c0,0,0.1,0,0.1,0l0.4-0.2c0,0,0.1,0,0.1,0C18.8,14.8,19,14.9,19.2,14.8L19.2,14.8L19.2,14.8z"/>
|
||||||
|
<path class="st15" d="M23,18.3l-0.3-0.1c0,0-0.1,0-0.1-0.1c0-0.1-0.1-0.2-0.1-0.3c0,0,0-0.1,0-0.1l0.2-0.3c0,0,0-0.1,0-0.1l-0.2-0.2
|
||||||
|
c0,0-0.1,0-0.1,0l-0.3,0.2c0,0-0.1,0-0.1,0c-0.1-0.1-0.2-0.1-0.3-0.1c0,0-0.1,0-0.1-0.1l-0.1-0.3c0,0,0-0.1-0.1-0.1h-0.3
|
||||||
|
c0,0-0.1,0-0.1,0.1l-0.1,0.3c0,0,0,0-0.1,0.1c0,0,0,0-0.1,0c-0.1,0-0.1,0-0.2,0.1c0,0-0.1,0-0.1,0c0,0,0,0,0,0c0,0,0,0,0,0
|
||||||
|
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l-0.3-0.2c0,0-0.1,0-0.1,0l-0.2,0.2h0l0,0h0c0,0,0,0,0,0c0,0,0,0,0,0.1l0.2,0.3c0,0,0,0,0,0
|
||||||
|
c0,0,0,0,0,0.1c-0.1,0.1-0.1,0.2-0.1,0.3c0,0,0,0-0.1,0.1l-0.3,0.1c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0v0.3c0,0,0,0,0,0
|
||||||
|
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0l0.3,0.1c0,0,0.1,0,0.1,0.1c0,0.1,0.1,0.2,0.1,0.3c0,0,0,0.1,0,0.1l-0.2,0.3c0,0,0,0,0,0.1
|
||||||
|
c0,0,0,0,0,0h0l0.2,0.2c0,0,0.1,0,0.1,0l0.3-0.2c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0c0.1,0,0.1,0.1,0.2,0.1c0,0,0,0,0.1,0
|
||||||
|
c0,0,0.1,0,0.1,0.1l0.1,0.3c0,0,0,0.1,0.1,0.1h0.3c0,0,0.1,0,0.1-0.1l0.1-0.3c0,0,0-0.1,0.1-0.1c0.1,0,0.2-0.1,0.3-0.1
|
||||||
|
c0,0,0.1,0,0.1,0l0.3,0.2c0,0,0.1,0,0.1,0l0.2-0.2c0,0,0-0.1,0-0.1l-0.2-0.3c0,0,0-0.1,0-0.1c0.1-0.1,0.1-0.2,0.1-0.3c0,0,0,0,0,0
|
||||||
|
c0,0,0,0,0,0l0.3-0.1c0,0,0.1,0,0.1-0.1L23,18.3C23.1,18.3,23.1,18.3,23,18.3L23,18.3z M21.4,19.3c-0.1,0-0.2,0-0.3,0
|
||||||
|
c-0.1,0-0.3-0.1-0.5-0.2c0,0,0,0,0,0c-0.1-0.1-0.2-0.3-0.2-0.4c0-0.1,0-0.2,0-0.3c0-0.4,0.3-0.7,0.6-0.8c0.1,0,0.2,0,0.4,0
|
||||||
|
c0,0,0,0,0,0c0.1,0,0.3,0.1,0.4,0.2c0,0,0,0,0,0c0,0.1,0.1,0.1,0.1,0.2l0,0c0,0.1,0.1,0.2,0.1,0.3C22.2,18.8,21.9,19.3,21.4,19.3
|
||||||
|
C21.4,19.3,21.4,19.3,21.4,19.3L21.4,19.3z"/>
|
||||||
|
<path class="st8" d="M20.7,18.5c0,0.3,0.2,0.5,0.5,0.6c0,0,0,0,0,0c0.3,0,0.6-0.3,0.6-0.6c0-0.3-0.2-0.5-0.5-0.6c0,0,0,0,0,0
|
||||||
|
C20.9,18,20.7,18.2,20.7,18.5z"/>
|
||||||
|
<path class="st16" d="M22.2,18.5c0-0.5-0.4-0.9-0.8-0.9c0.1,0,0.1,0.1,0.1,0.2c0,0-0.1,0.1-0.1,0.1c0.3,0,0.5,0.3,0.5,0.6
|
||||||
|
c0,0.3-0.3,0.6-0.6,0.6c0,0,0,0,0,0c-0.1,0-0.1,0.1-0.1,0.2c0,0.1,0.1,0.1,0.1,0.2c0,0,0,0,0,0C21.8,19.4,22.2,19,22.2,18.5
|
||||||
|
L22.2,18.5z"/>
|
||||||
|
<path class="st17" d="M21.3,17.6c-0.1,0-0.2,0-0.2,0V18c0.1,0,0.1,0,0.2,0c0.3,0,0.5,0.2,0.6,0.5c0-0.1,0.1-0.2,0.2-0.1
|
||||||
|
c0.1,0,0.1,0.1,0.1,0.1c0,0,0,0,0,0C22.1,18,21.7,17.6,21.3,17.6L21.3,17.6z"/>
|
||||||
|
<path class="st16" d="M20.7,18.7c0-0.1,0-0.1,0-0.2c0-0.3,0.3-0.6,0.6-0.6c0,0,0,0,0,0c0.1,0,0.2-0.1,0.1-0.2c0-0.1-0.1-0.1-0.1-0.1
|
||||||
|
c0,0,0,0-0.1,0c-0.5,0-0.9,0.4-0.9,0.9c0,0.1,0,0.2,0,0.2C20.4,18.7,20.7,18.7,20.7,18.7z"/>
|
||||||
|
<path class="st17" d="M21.3,19.1C21.3,19.1,21.3,19.1,21.3,19.1c-0.3,0-0.6-0.2-0.6-0.6c0,0,0,0,0-0.1h-0.3c0,0,0,0,0,0.1
|
||||||
|
c0,0.5,0.4,0.9,0.9,0.9c0,0,0,0,0,0c0.1,0,0.1-0.1,0.1-0.2C21.4,19.2,21.4,19.1,21.3,19.1L21.3,19.1z"/>
|
||||||
|
<path class="st8" d="M21.9,18.5c0,0.3-0.3,0.6-0.6,0.6c0,0,0,0,0,0c-0.4,0-0.6-0.3-0.6-0.6c0-0.3,0.3-0.6,0.6-0.6c0,0,0,0,0,0
|
||||||
|
C21.6,17.9,21.9,18.2,21.9,18.5L21.9,18.5z"/>
|
||||||
|
<path class="st18" d="M20.9,19.9c-0.4-0.3-0.7-0.8-0.7-1.3c0-0.6,0.3-1.1,0.7-1.3v-0.1c-0.1,0-0.2,0.1-0.4,0.1c0,0-0.1,0-0.1,0
|
||||||
|
l-0.3-0.2c0,0-0.1,0-0.1,0l-0.2,0.2c0,0,0,0.1,0,0.1l0.2,0.3c0,0,0,0.1,0,0.1c-0.1,0.1-0.1,0.2-0.1,0.3c0,0,0,0.1-0.1,0.1l-0.3,0.1
|
||||||
|
c0,0-0.1,0-0.1,0.1v0.3c0,0,0,0.1,0.1,0.1l0.3,0.1c0,0,0.1,0,0.1,0.1c0,0.1,0.1,0.2,0.1,0.3c0,0,0,0.1,0,0.1l-0.2,0.3
|
||||||
|
c0,0,0,0.1,0,0.1l0.2,0.2c0,0,0.1,0,0.1,0l0.3-0.2c0,0,0.1,0,0.1,0C20.7,19.8,20.8,19.9,20.9,19.9C20.9,19.9,20.9,19.9,20.9,19.9z"
|
||||||
|
/>
|
||||||
|
<path class="st15" d="M19.3,16.9l-0.9-0.1c0,0-0.1,0-0.1-0.1c0-0.1,0-0.1-0.1-0.2c0,0,0-0.1,0-0.1l0.6-0.6c0,0,0-0.1,0-0.2l-0.4-0.5
|
||||||
|
c0,0-0.1-0.1-0.2,0l-0.8,0.4c0,0-0.1,0-0.1,0c-0.1,0-0.1-0.1-0.2-0.1c0,0-0.1-0.1-0.1-0.1l0.1-0.9c0-0.1,0-0.1-0.1-0.1L16.7,14
|
||||||
|
c-0.1,0-0.1,0-0.1,0.1l-0.4,0.8c0,0-0.1,0.1-0.1,0.1c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1,0c0,0-0.1,0-0.1-0.1l-0.1-0.3L15.3,14
|
||||||
|
c0-0.1-0.1-0.1-0.1-0.1l-0.6,0.2c-0.1,0-0.1,0.1-0.1,0.1l0.1,0.9c0,0,0,0.1-0.1,0.1c-0.1,0-0.1,0.1-0.2,0.1c0,0-0.1,0-0.1,0L13.5,15
|
||||||
|
c-0.1,0-0.1,0-0.2,0L13,15.6c0,0,0,0.1,0,0.2l0.6,0.6c0,0,0,0.1,0,0.1c0,0.1,0,0.1-0.1,0.2c0,0-0.1,0.1-0.1,0.1l-0.7,0.1
|
||||||
|
c0,0.3,0.1,0.6,0.2,1l0.5,0.1c0,0,0.1,0,0.1,0.1c0,0.1,0,0.1,0.1,0.2c0,0,0,0.1,0,0.1l-0.3,0.3c0.2,0.3,0.4,0.6,0.6,0.8l0.3-0.2
|
||||||
|
c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0.1,0.2,0.1c0,0,0.1,0.1,0.1,0.1L14.6,20c0.3,0.2,0.5,0.4,0.8,0.5l0.3-0.6
|
||||||
|
c0,0,0.1-0.1,0.1-0.1h0.2c0,0,0.1,0,0.1,0.1l0.4,0.8c0,0.1,0.1,0.1,0.1,0.1l0.6-0.2c0.1,0,0.1-0.1,0.1-0.1l-0.1-0.9
|
||||||
|
c0-0.1,0-0.1,0.1-0.1c0.1,0,0.1-0.1,0.2-0.1c0,0,0.1,0,0.1,0l0.8,0.4c0.1,0,0.1,0,0.2,0l0.4-0.5c0-0.1,0-0.1,0-0.2l-0.6-0.6
|
||||||
|
c0,0,0-0.1,0-0.1c0-0.1,0.1-0.1,0.1-0.2c0,0,0-0.1,0.1-0.1l0.9-0.1c0.1,0,0.1-0.1,0.1-0.1v-0.6C19.4,17,19.4,17,19.3,16.9z
|
||||||
|
M15.2,18.3C15.2,18.3,15.2,18.2,15.2,18.3c-0.1-0.1-0.1-0.1-0.2-0.2c0,0-0.1-0.1-0.1-0.1c0-0.1-0.1-0.1-0.1-0.2c0-0.1,0-0.1,0-0.2
|
||||||
|
c0-0.1,0-0.2,0-0.2v0c0-0.6,0.5-1.2,1.2-1.2c0,0,0.1,0,0.1,0l0.1,0c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0s0.1,0,0.1,0
|
||||||
|
l0.1,0c0,0,0,0,0.1,0.1l0.1,0.1c0.1,0.1,0.1,0.1,0.1,0.2c0,0,0,0.1,0.1,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1
|
||||||
|
c0,0.1,0,0.2,0,0.2l0,0.2c0,0,0,0.1,0,0.1l-0.2,0.4c0,0,0,0.1-0.1,0.1c0,0,0,0-0.1,0.1c0,0-0.1,0.1-0.1,0.1c0,0-0.1,0-0.1,0.1
|
||||||
|
c-0.1,0-0.1,0.1-0.2,0.1c0,0-0.1,0-0.1,0C15.9,18.6,15.6,18.5,15.2,18.3C15.3,18.3,15.3,18.3,15.2,18.3z"/>
|
||||||
|
<path class="st18" d="M15.3,14c0-0.1-0.1-0.1-0.1-0.1l-0.6,0.2c-0.1,0-0.1,0.1-0.1,0.1l0.1,0.9c0,0,0,0.1-0.1,0.1
|
||||||
|
c-0.1,0-0.1,0.1-0.2,0.1c0,0-0.1,0-0.1,0L13.5,15c-0.1,0-0.1,0-0.2,0L13,15.6c0,0,0,0.1,0,0.2l0.6,0.6c0,0,0,0.1,0,0.1
|
||||||
|
c0,0.1,0,0.1-0.1,0.2c0,0-0.1,0.1-0.1,0.1l-0.7,0.1c0,0.3,0.1,0.6,0.2,1l0.5,0.1c0,0,0.1,0,0.1,0.1c0,0.1,0,0.1,0.1,0.2
|
||||||
|
c0,0,0,0.1,0,0.1l-0.3,0.3c0.2,0.3,0.4,0.6,0.6,0.8l0.3-0.2c0,0,0.1,0,0.1,0c0,0,0,0,0,0l0,0v0c-0.1-0.4-0.2-0.9-0.2-1.4
|
||||||
|
c0,0,0,0,0-0.1c0-0.2,0-0.4,0-0.6c0-0.3,0.1-0.6,0.2-0.9c0,0,0-0.1,0-0.1c0.2-0.7,0.6-1.4,1.1-1.8L15.3,14z"/>
|
||||||
|
<path class="st17" d="M17.2,16.1c-0.3-0.3-0.8-0.5-1.2-0.5c-0.1,0-0.2,0-0.3,0c-0.5,0.1-0.9,0.4-1.2,0.8c-0.2,0.3-0.3,0.6-0.3,0.9
|
||||||
|
c0,0.2,0,0.3,0.1,0.5c0.1,0.3,0.2,0.6,0.5,0.8c0.4,0.4,0.9,0.6,1.5,0.5c0.4,0,0.8-0.2,1-0.5c0.2-0.2,0.4-0.6,0.5-0.9v0
|
||||||
|
c0-0.1,0-0.2,0-0.3C17.7,16.9,17.5,16.4,17.2,16.1z M15.3,18.3C15.3,18.3,15.3,18.3,15.3,18.3c-0.1-0.1-0.1-0.1-0.2-0.1
|
||||||
|
c0,0-0.1-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c0-0.1-0.1-0.1-0.1-0.2c0-0.1,0-0.1,0-0.2c0-0.1,0-0.2,0-0.2v0c0-0.6,0.5-1.2,1.2-1.2
|
||||||
|
c0,0,0.1,0,0.1,0l0.1,0c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0l0.1,0c0,0,0,0,0.1,0.1l0.1,0.1
|
||||||
|
c0.1,0.1,0.1,0.1,0.1,0.2c0,0,0,0.1,0.1,0.1s0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.1,0,0.2,0,0.2l0,0.2c0,0,0,0.1,0,0.1
|
||||||
|
l-0.2,0.4c0,0,0,0.1-0.1,0.1c0,0,0,0-0.1,0.1c0,0-0.1,0.1-0.1,0.1c0,0-0.1,0-0.1,0.1c-0.1,0-0.1,0.1-0.2,0.1c0,0-0.1,0-0.1,0
|
||||||
|
c-0.1,0-0.2,0-0.2,0C15.7,18.5,15.5,18.5,15.3,18.3z"/>
|
||||||
|
<path class="st16" d="M14.5,17.4c0.1,0,0.3-0.1,0.3-0.2c0-0.2,0.2-0.4,0.3-0.6c0.2-0.2,0.4-0.3,0.6-0.3c0.1,0,0.2-0.1,0.2-0.3
|
||||||
|
c0-0.2-0.2-0.3-0.3-0.3c-0.3,0.1-0.7,0.2-0.9,0.5c-0.2,0.2-0.4,0.6-0.5,0.9C14.2,17.2,14.3,17.4,14.5,17.4z M17.4,17.4
|
||||||
|
c-0.1,0-0.3,0.1-0.3,0.2c0,0.2-0.2,0.4-0.3,0.6c-0.2,0.2-0.4,0.3-0.6,0.3c-0.1,0-0.2,0.1-0.2,0.3c0,0.2,0.2,0.3,0.3,0.3
|
||||||
|
c0.3-0.1,0.7-0.2,0.9-0.5c0.2-0.2,0.4-0.6,0.5-0.9C17.7,17.6,17.6,17.4,17.4,17.4z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 14 KiB |
8709
addons/cetmix_tower/static/description/tower.svg
Normal file
|
After Width: | Height: | Size: 988 KiB |
3201
addons/cetmix_tower/static/description/tower_website.svg
Normal file
|
After Width: | Height: | Size: 333 KiB |
BIN
addons/cetmix_tower/static/description/yaml_1.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
addons/cetmix_tower/static/description/yaml_2.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
addons/cetmix_tower/static/description/yaml_3.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
addons/cetmix_tower/static/description/yaml_4.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
addons/cetmix_tower/static/description/yaml_5.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
125
addons/cetmix_tower_git/README.rst
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
================
|
||||||
|
Cetmix Tower Git
|
||||||
|
================
|
||||||
|
|
||||||
|
..
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
|
!! changes will be overwritten. !!
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! source digest: sha256:583744e8956f294682a551fc082f086b174b8d2b72652c21b1dd68f3933e7211
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||||
|
:target: https://odoo-community.org/page/development-status
|
||||||
|
:alt: Beta
|
||||||
|
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
|
||||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
|
:alt: License: AGPL-3
|
||||||
|
.. |badge3| image:: https://img.shields.io/badge/github-cetmix%2Fcetmix--tower-lightgray.png?logo=github
|
||||||
|
:target: https://github.com/cetmix/cetmix-tower/tree/16.0/cetmix_tower_git
|
||||||
|
:alt: cetmix/cetmix-tower
|
||||||
|
|
||||||
|
|badge1| |badge2| |badge3|
|
||||||
|
|
||||||
|
This module implements Git Management functionality for `Cetmix
|
||||||
|
Tower <https://cetmix.com/tower>`__.
|
||||||
|
|
||||||
|
Please refer to the `official
|
||||||
|
documentation <https://cetmix.com/tower>`__ for detailed information.
|
||||||
|
|
||||||
|
**Table of contents**
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
|
Please refer to the `official
|
||||||
|
documentation <https://cetmix.com/tower>`__ for detailed configuration
|
||||||
|
instructions.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
Please refer to the `official
|
||||||
|
documentation <https://cetmix.com/tower>`__ for detailed usage
|
||||||
|
instructions.
|
||||||
|
|
||||||
|
Changelog
|
||||||
|
=========
|
||||||
|
|
||||||
|
16.0.2.0.1 (2025-12-11)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- Features: Improve search views, implement the search panel for
|
||||||
|
selected views. (5139)
|
||||||
|
|
||||||
|
16.0.2.0.0 (2025-10-27)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- Features: Major refactoring: implement Git repository entity. (4914)
|
||||||
|
|
||||||
|
16.0.1.0.6 (2025-08-18)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- Features: Link or copy a git project when uploading the linked file
|
||||||
|
using command (4759)
|
||||||
|
|
||||||
|
16.0.1.0.5 (2025-08-17)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- Features: Search servers by git reference (4838)
|
||||||
|
|
||||||
|
16.0.1.0.4 (2025-07-29)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- Features: Export related commands and flight plans together with
|
||||||
|
server (4849)
|
||||||
|
|
||||||
|
16.0.1.0.3 (2025-05-23)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- Bugfixes: Duplicated file is created when importing a YAML file with a
|
||||||
|
git project. (4715)
|
||||||
|
|
||||||
|
16.0.1.0.2 (2025-05-16)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- Features: Record references for git relations. (4670)
|
||||||
|
|
||||||
|
16.0.1.0.1 (2025-05-09)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- Bugfixes: Non-critical issues and performance improvements. (4663)
|
||||||
|
|
||||||
|
16.0.1.0.0
|
||||||
|
----------
|
||||||
|
|
||||||
|
Release for Odoo 16.0
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
===========
|
||||||
|
|
||||||
|
Bugs are tracked on `GitHub Issues <https://github.com/cetmix/cetmix-tower/issues>`_.
|
||||||
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
|
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||||
|
`feedback <https://github.com/cetmix/cetmix-tower/issues/new?body=module:%20cetmix_tower_git%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||||
|
|
||||||
|
Do not contact contributors directly about support or help with technical issues.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Authors
|
||||||
|
-------
|
||||||
|
|
||||||
|
* Cetmix
|
||||||
|
|
||||||
|
Maintainers
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This module is part of the `cetmix/cetmix-tower <https://github.com/cetmix/cetmix-tower/tree/16.0/cetmix_tower_git>`_ project on GitHub.
|
||||||
|
|
||||||
|
You are welcome to contribute.
|
||||||
1
addons/cetmix_tower_git/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
40
addons/cetmix_tower_git/__manifest__.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Copyright Cetmix OU
|
||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
{
|
||||||
|
"name": "Cetmix Tower Git",
|
||||||
|
"summary": "Cetmix Tower Git Management Tools",
|
||||||
|
"version": "16.0.2.0.2",
|
||||||
|
"development_status": "Beta",
|
||||||
|
"category": "Productivity",
|
||||||
|
"website": "https://tower.cetmix.com",
|
||||||
|
"author": "Cetmix",
|
||||||
|
"license": "AGPL-3",
|
||||||
|
"application": False,
|
||||||
|
"depends": ["cetmix_tower_yaml"],
|
||||||
|
"external_dependencies": {
|
||||||
|
"python": ["giturlparse==0.12.0"],
|
||||||
|
},
|
||||||
|
"data": [
|
||||||
|
"security/ir.model.access.csv",
|
||||||
|
"security/cx_tower_git_project_security.xml",
|
||||||
|
"security/cx_tower_git_source_security.xml",
|
||||||
|
"security/cx_tower_git_remote_security.xml",
|
||||||
|
"security/cx_tower_git_repo_security.xml",
|
||||||
|
"security/cx_tower_git_repo_owner_security.xml",
|
||||||
|
"security/cx_tower_git_project_rel_security.xml",
|
||||||
|
"security/cx_tower_git_project_file_template_rel_security.xml",
|
||||||
|
"views/cx_tower_git_project_views.xml",
|
||||||
|
"views/cx_tower_git_source_views.xml",
|
||||||
|
"views/cx_tower_git_remote_views.xml",
|
||||||
|
"views/cx_tower_git_repo_views.xml",
|
||||||
|
"views/cx_tower_git_repo_owner_views.xml",
|
||||||
|
"views/cx_tower_file_views.xml",
|
||||||
|
"views/cx_tower_file_template_views.xml",
|
||||||
|
"views/cx_tower_server_view.xml",
|
||||||
|
"views/cx_tower_plan_line_view.xml",
|
||||||
|
"views/menuitems.xml",
|
||||||
|
],
|
||||||
|
"demo": [
|
||||||
|
"demo/demo_data.xml",
|
||||||
|
],
|
||||||
|
}
|
||||||
166
addons/cetmix_tower_git/demo/demo_data.xml
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo noupdate="1">
|
||||||
|
|
||||||
|
<!-- Git Project -->
|
||||||
|
<record id="git_project_demo" model="cx.tower.git.project">
|
||||||
|
<field name="name">Demo Git Project</field>
|
||||||
|
<field name="reference">demo_git_project</field>
|
||||||
|
<field name="note">This is a demo git project.</field>
|
||||||
|
</record>
|
||||||
|
<!-- Repositories -->
|
||||||
|
<record id="repo_demo_cetmix_tower" model="cx.tower.git.repo">
|
||||||
|
<field name="url">https://github.com/cetmix-demo/cetmix-tower-demo.git</field>
|
||||||
|
</record>
|
||||||
|
<record id="repo_demo_oca_web" model="cx.tower.git.repo">
|
||||||
|
<field name="url">https://github.com/oca-demo/web-demo.git</field>
|
||||||
|
</record>
|
||||||
|
<record id="repo_demo_odoo_enterprise" model="cx.tower.git.repo">
|
||||||
|
<field name="url">https://github.com/odoo-demo/enterprise-demo.git</field>
|
||||||
|
<field name="is_private" eval="True" />
|
||||||
|
</record>
|
||||||
|
<record id="repo_demo_gitlab_private" model="cx.tower.git.repo">
|
||||||
|
<field name="url">https://gitlab.com/cetmix-demo/cetmix-tower-demo.git</field>
|
||||||
|
<field name="is_private" eval="True" />
|
||||||
|
</record>
|
||||||
|
<record id="repo_demo_bitbucket_private" model="cx.tower.git.repo">
|
||||||
|
<field
|
||||||
|
name="url"
|
||||||
|
>https://bitbucket.com/cetmix-demo/cetmix-tower-demo-enterprise.git</field>
|
||||||
|
<field name="is_private" eval="True" />
|
||||||
|
</record>
|
||||||
|
<!-- Sources -->
|
||||||
|
<!-- Cetmix Tower -->
|
||||||
|
<record id="source_demo_cetmix_tower" model="cx.tower.git.source">
|
||||||
|
<field name="name">Cetmix Tower</field>
|
||||||
|
<field name="reference">cetmix_tower</field>
|
||||||
|
<field name="git_project_id" ref="git_project_demo" />
|
||||||
|
</record>
|
||||||
|
<!-- Remotes-->
|
||||||
|
<record id="remote_demo_cetmix_tower_14_0_dev" model="cx.tower.git.remote">
|
||||||
|
<field name="source_id" ref="source_demo_cetmix_tower" />
|
||||||
|
<field name="repo_id" ref="repo_demo_cetmix_tower" />
|
||||||
|
<field name="head_type">branch</field>
|
||||||
|
<field name="head">14.0</field>
|
||||||
|
</record>
|
||||||
|
<record id="remote_demo_cetmix_tower_pr_176" model="cx.tower.git.remote">
|
||||||
|
<field name="source_id" ref="source_demo_cetmix_tower" />
|
||||||
|
<field name="repo_id" ref="repo_demo_cetmix_tower" />
|
||||||
|
<field name="head_type">pr</field>
|
||||||
|
<field name="head">176</field>
|
||||||
|
</record>
|
||||||
|
<!-- OCA Web -->
|
||||||
|
<record id="source_demo_oca_web" model="cx.tower.git.source">
|
||||||
|
<field name="name">OCA Web</field>
|
||||||
|
<field name="reference">oca_web</field>
|
||||||
|
<field name="git_project_id" ref="git_project_demo" />
|
||||||
|
</record>
|
||||||
|
<!-- Remotes -->
|
||||||
|
<record id="remote_demo_oca_web_14_0" model="cx.tower.git.remote">
|
||||||
|
<field name="source_id" ref="source_demo_oca_web" />
|
||||||
|
<field name="repo_id" ref="repo_demo_oca_web" />
|
||||||
|
<field name="head_type">branch</field>
|
||||||
|
<field name="head">14.0</field>
|
||||||
|
</record>
|
||||||
|
<!-- Odoo Enterprise -->
|
||||||
|
<record id="source_demo_odoo_enterprise" model="cx.tower.git.source">
|
||||||
|
<field name="name">Odoo Enterprise (Private)</field>
|
||||||
|
<field name="reference">odoo_enterprise</field>
|
||||||
|
<field name="git_project_id" ref="git_project_demo" />
|
||||||
|
</record>
|
||||||
|
<!-- Remotes -->
|
||||||
|
<record id="remote_demo_odoo_enterprise" model="cx.tower.git.remote">
|
||||||
|
<field name="source_id" ref="source_demo_odoo_enterprise" />
|
||||||
|
<field name="repo_id" ref="repo_demo_odoo_enterprise" />
|
||||||
|
<field name="head_type">branch</field>
|
||||||
|
<field name="head">19.0</field>
|
||||||
|
<field name="is_private" eval="True" />
|
||||||
|
</record>
|
||||||
|
<!-- Sample Private Gitlab -->
|
||||||
|
<record id="source_demo_gitlab_private" model="cx.tower.git.source">
|
||||||
|
<field name="name">Sample Semi Private Gitlab</field>
|
||||||
|
<field name="reference">gitlab_private</field>
|
||||||
|
<field name="git_project_id" ref="git_project_demo" />
|
||||||
|
</record>
|
||||||
|
<!-- Remotes -->
|
||||||
|
<record id="remote_demo_gitlab_private_main" model="cx.tower.git.remote">
|
||||||
|
<field name="source_id" ref="source_demo_gitlab_private" />
|
||||||
|
<field name="repo_id" ref="repo_demo_gitlab_private" />
|
||||||
|
<field name="head_type">branch</field>
|
||||||
|
<field name="head">main</field>
|
||||||
|
</record>
|
||||||
|
<record id="remote_demo_gitlab_private_mr_1234" model="cx.tower.git.remote">
|
||||||
|
<field name="source_id" ref="source_demo_gitlab_private" />
|
||||||
|
<field name="repo_id" ref="repo_demo_gitlab_private" />
|
||||||
|
<field name="head_type">pr</field>
|
||||||
|
<field name="head">1234</field>
|
||||||
|
</record>
|
||||||
|
<!-- Sample Private Bitbucket -->
|
||||||
|
<record id="source_demo_bitbucket_private" model="cx.tower.git.source">
|
||||||
|
<field name="name">Sample Private Bitbucket</field>
|
||||||
|
<field name="reference">bitbucket_private</field>
|
||||||
|
<field name="git_project_id" ref="git_project_demo" />
|
||||||
|
</record>
|
||||||
|
<!-- Remotes -->
|
||||||
|
<record id="remote_demo_bitbucket_private_main" model="cx.tower.git.remote">
|
||||||
|
<field name="source_id" ref="source_demo_bitbucket_private" />
|
||||||
|
<field name="repo_id" ref="repo_demo_bitbucket_private" />
|
||||||
|
<field name="head_type">branch</field>
|
||||||
|
<field name="head">dev</field>
|
||||||
|
</record>
|
||||||
|
<record id="remote_demo_bitbucket_private_feature" model="cx.tower.git.remote">
|
||||||
|
<field name="source_id" ref="source_demo_bitbucket_private" />
|
||||||
|
<field name="repo_id" ref="repo_demo_bitbucket_private" />
|
||||||
|
<field name="head_type">commit</field>
|
||||||
|
<field name="head">1234567890</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Files -->
|
||||||
|
<record id="file_demo_cetmix_tower_14_0_dev" model="cx.tower.file">
|
||||||
|
<field name="name">repos.yaml</field>
|
||||||
|
<field name="server_id" ref="cetmix_tower_server.server_demo_1" />
|
||||||
|
<field name="source">tower</field>
|
||||||
|
<field name="file_type">text</field>
|
||||||
|
<field name="server_dir">{{ instance_name }}/config</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Link file to git project -->
|
||||||
|
<record
|
||||||
|
id="git_project_rel_demo_cetmix_tower_14_0_dev"
|
||||||
|
model="cx.tower.git.project.rel"
|
||||||
|
>
|
||||||
|
<field name="git_project_id" ref="git_project_demo" />
|
||||||
|
<field name="server_id" ref="cetmix_tower_server.server_demo_1" />
|
||||||
|
<field name="file_id" ref="file_demo_cetmix_tower_14_0_dev" />
|
||||||
|
<field name="project_format">git_aggregator</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Demo variable for testing giturlparse -->
|
||||||
|
<record id="variable_demo_git_url" model="cx.tower.variable">
|
||||||
|
<field name="name">Demo Git URL</field>
|
||||||
|
<field name="reference">demo_git_url</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Demo command to test giturlparse -->
|
||||||
|
<record id="command_demo_git_url" model="cx.tower.command">
|
||||||
|
<field name="name">Parse Git URL</field>
|
||||||
|
<field name="action">python_code</field>
|
||||||
|
<field name="code">
|
||||||
|
if {{ demo_git_url }}:
|
||||||
|
parsed_url = giturlparse.parse({{ demo_git_url }})
|
||||||
|
repo = parsed_url.repo
|
||||||
|
owner = parsed_url.owner
|
||||||
|
host = parsed_url.host
|
||||||
|
platform = parsed_url.platform
|
||||||
|
message = "Repo: " + repo + ", Owner: " + owner + ", Host: " + host + ", Platform: " + platform
|
||||||
|
result={"exit_code": 0, "message": message}
|
||||||
|
else:
|
||||||
|
result={"exit_code": -100, "message": "Git URL is not defined!"}
|
||||||
|
</field>
|
||||||
|
<field name="access_level">1</field>
|
||||||
|
<field
|
||||||
|
name="tag_ids"
|
||||||
|
eval="[(6, 0, [ref('cetmix_tower_server.tag_custom')])]"
|
||||||
|
/>
|
||||||
|
<field name="note">Run Python Code: Check Branch</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
1029
addons/cetmix_tower_git/i18n/cetmix_tower_git.pot
Normal file
595
addons/cetmix_tower_git/i18n/fi.po
Normal file
@@ -0,0 +1,595 @@
|
|||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * cetmix_tower_git
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 16.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"Last-Translator: Automatically generated\n"
|
||||||
|
"Language-Team: none\n"
|
||||||
|
"Language: fi\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
"# You need to set the following variables in your environment:\n"
|
||||||
|
"# %(vars)s \n"
|
||||||
|
"# and run git-aggregator with '--expand-env' parameter.\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"# This file is generated with Cetmix Tower https://cetmix.com/tower\n"
|
||||||
|
"# It's designed to be used with git-aggregator tool developed by Acsone.\n"
|
||||||
|
"# Documentation for git-aggregator: https://github.com/acsone/git-aggregator\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "* Sources where all remotes are private"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "* Sources where some remotes are private"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid ""
|
||||||
|
"<b>Managers.</b> All users who have \"Manager\" group and are set as \"Managers\" in <b><u>all</u></b> related servers.\n"
|
||||||
|
" This is done to avoid unpredictable consequences when some of the servers are not updated due to access restrictions when a project is updated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid ""
|
||||||
|
"<b>Users.</b> All users who have \"Manager\" group and are either set in "
|
||||||
|
"\"Users\" or in \"Managers\" in <b><u>all</u></b> related servers."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "Access"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__active
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__active
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__active
|
||||||
|
msgid "Active"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "Archived"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__bitbucket
|
||||||
|
msgid "Bitbucket"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__branch
|
||||||
|
msgid "Branch"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form
|
||||||
|
msgid "Branch/PR/commit number or link"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__reference
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__reference
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__reference
|
||||||
|
msgid ""
|
||||||
|
"Can contain English letters, digits and '_'. Leave blank to autogenerate"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project
|
||||||
|
msgid "Cetmix Tower Git Configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_remote
|
||||||
|
msgid "Cetmix Tower Git Remote"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_source
|
||||||
|
msgid "Cetmix Tower Git Source"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_server
|
||||||
|
msgid "Cetmix Tower Server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Code generator function for '%(project_format)s' format not found."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__commit
|
||||||
|
msgid "Commit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_uid
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_uid
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_uid
|
||||||
|
msgid "Created by"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_date
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_date
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_date
|
||||||
|
msgid "Created on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_file
|
||||||
|
msgid "Cx Tower File"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid "Disabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__display_name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__display_name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__display_name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__display_name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__display_name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__display_name
|
||||||
|
msgid "Display Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__enabled
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__enabled
|
||||||
|
msgid "Enable in configuration and exported to files"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__enabled
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__enabled
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "Export YAML"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__file_id
|
||||||
|
msgid "File"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "File '%(file)s' doesn't belong to server '%(server)s'"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_file.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"File '%(file)s' is related to multiple projects: %(projects)s \n"
|
||||||
|
"Please select only one project."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.constraint,message:cetmix_tower_git.constraint_cx_tower_git_project_rel_project_server_file_format_uniq
|
||||||
|
msgid "File is already related to the same project and format"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__file_ids
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "Files"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__project_format
|
||||||
|
msgid "Format"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir
|
||||||
|
msgid "Git Aggregator Root Dir"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Git Aggregator: Bitbucket does not support fetching PRs. Please use branch instead.\n"
|
||||||
|
"\n"
|
||||||
|
"Source: %(src)s\n"
|
||||||
|
"URL: %(url)s\n"
|
||||||
|
"Head: %(head)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Git Aggregator: Head number is empty in %(head)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__git_project_id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__git_project_id
|
||||||
|
msgid "Git Configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__git_project_id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_ids
|
||||||
|
msgid "Git Project"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_rel_ids
|
||||||
|
msgid "Git Project Rel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_project_rel_ids
|
||||||
|
msgid "Git Project Server File Relations"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project_rel
|
||||||
|
msgid "Git Project relation to other model records"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.actions.act_window,name:cetmix_tower_git.cx_tower_git_project_action
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_ids
|
||||||
|
#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_project
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_server_view_form
|
||||||
|
msgid "Git Projects"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir
|
||||||
|
msgid ""
|
||||||
|
"Git aggregator root directory where sources will be cloned. Eg '/tmp/git-"
|
||||||
|
"aggregator' Will use '.' if not set"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid ""
|
||||||
|
"Git aggregator root directory where sources will be cloned. Leave blank to "
|
||||||
|
"use '.'"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__url
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form
|
||||||
|
msgid ""
|
||||||
|
"Git remote URL. Eg 'https://github.com/cetmix/cetmix-tower.git' or "
|
||||||
|
"'git@github.com:cetmix/cetmix-tower.git'"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__head
|
||||||
|
msgid ""
|
||||||
|
"Git remote head. Link to branch, PR, commit or commit hash. Leave blank to "
|
||||||
|
"auto-detect"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__github
|
||||||
|
msgid "GitHub"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__gitlab
|
||||||
|
msgid "GitLab"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__https
|
||||||
|
msgid "HTTPS"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes
|
||||||
|
msgid "Has Partially Private Remotes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes
|
||||||
|
msgid "Has Private Remotes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head
|
||||||
|
msgid "Head"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head_type
|
||||||
|
msgid "Head Type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__id
|
||||||
|
msgid "ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes
|
||||||
|
msgid "Indicates if the project has any partially private remotes."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes
|
||||||
|
msgid "Indicates if the project has any private remotes."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__is_private
|
||||||
|
msgid "Is Private"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file____last_update
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project____last_update
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel____last_update
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote____last_update
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source____last_update
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server____last_update
|
||||||
|
msgid "Last Modified on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_uid
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_uid
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_uid
|
||||||
|
msgid "Last Updated by"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_date
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_date
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_date
|
||||||
|
msgid "Last Updated on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__manager_ids
|
||||||
|
msgid "Managers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__manager_ids
|
||||||
|
msgid "Managers who can modify this record"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__name
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid "Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Not a valid URL. URL must end with '.git'"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Not a valid URL. URL must start with 'https://' or 'git@'"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Not a valid URL: %(url_msg)s\n"
|
||||||
|
"URL must contain at least two parts separated by dot."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__other
|
||||||
|
msgid "Other"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid "Private"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count_private
|
||||||
|
msgid "Private Remotes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__pr
|
||||||
|
msgid "Pull/Merge Request"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__reference
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__reference
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__reference
|
||||||
|
msgid "Reference"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid ""
|
||||||
|
"Reference. Can contain English letters, digits and '_'. Leave blank to "
|
||||||
|
"autogenerate"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_ids
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid "Remotes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__repo_provider
|
||||||
|
msgid "Repository Provider"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__is_private
|
||||||
|
msgid "Repository is private"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__ssh
|
||||||
|
msgid "SSH"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__sequence
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__sequence
|
||||||
|
msgid "Sequence"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__server_id
|
||||||
|
msgid "Server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__server_ids
|
||||||
|
msgid "Servers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__server_ids
|
||||||
|
msgid ""
|
||||||
|
"Servers are added automatically based on the files linked to the project.\n"
|
||||||
|
"IMPORTANT: This field may contain duplicates because of the relation nature!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__source_id
|
||||||
|
msgid "Source"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__source_ids
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "Sources"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid ""
|
||||||
|
"The top one remote will be used as a merge target.\n"
|
||||||
|
" You can re-arrange remotes by dragging them or changing their sequence value."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__url
|
||||||
|
msgid "URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__url_protocol
|
||||||
|
msgid "URL Protocol"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "URL is required"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__user_ids
|
||||||
|
msgid "Users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__user_ids
|
||||||
|
msgid "Users who can view this record"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__repo_provider
|
||||||
|
msgid ""
|
||||||
|
"Will be tried to be determined from the URL. Please select manually if auto-"
|
||||||
|
"detection fails."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "YAML"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__yaml_code
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__yaml_code
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__yaml_code
|
||||||
|
msgid "Yaml Code"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid ""
|
||||||
|
"You can edit these fields at your own risk. However keep in mind that they "
|
||||||
|
"will be automatically updated each time related servers are added, removed "
|
||||||
|
"or updated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "You must be a member of the \"YAML/Export\" group to export data as YAML."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "managers who can modify this record"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "users who can view this record"
|
||||||
|
msgstr ""
|
||||||
635
addons/cetmix_tower_git/i18n/hr.po
Normal file
@@ -0,0 +1,635 @@
|
|||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * cetmix_tower_git
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 16.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"PO-Revision-Date: 2025-03-06 09:11+0000\n"
|
||||||
|
"Last-Translator: Bole <bole@dajmi5.com>\n"
|
||||||
|
"Language-Team: Croatian <https://hosted.weblate.org/projects/"
|
||||||
|
"tower-server-14-0-dev/cetmix_tower_git/hr/>\n"
|
||||||
|
"Language: hr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||||
|
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
|
"X-Generator: Weblate 5.10.3-dev\n"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
"# You need to set the following variables in your environment:\n"
|
||||||
|
"# %(vars)s \n"
|
||||||
|
"# and run git-aggregator with '--expand-env' parameter.\n"
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
"# Potrebno je postaviti sljedeće varijable u vaše okruženje:\n"
|
||||||
|
"# %(vars)s \n"
|
||||||
|
"# i pokrenuti git-aggregator sa ' --expand-env' parametrom\n"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_project.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"# This file is generated with Cetmix Tower https://cetmix.com/tower\n"
|
||||||
|
"# It's designed to be used with git-aggregator tool developed by Acsone.\n"
|
||||||
|
"# Documentation for git-aggregator: https://github.com/acsone/git-aggregator\n"
|
||||||
|
msgstr ""
|
||||||
|
"# Ova datotek aje generiran pomoću Cetmix Tower sustava: https://cetmix.com/"
|
||||||
|
"tower\n"
|
||||||
|
"# Dizajniran je za korištenje sa git-aggregator alatom razvijenim od Ascone."
|
||||||
|
"\n"
|
||||||
|
"# Dokumentacija za git-aggregator : https://github.com/acsone/git-"
|
||||||
|
"aggregator\n"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "* Sources where all remotes are private"
|
||||||
|
msgstr "* izvori sa svim udaljenim lokacijama koje su privatne"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "* Sources where some remotes are private"
|
||||||
|
msgstr "* Izvori u kojima su neke udaljene lokacije privatne"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid ""
|
||||||
|
"<b>Managers.</b> All users who have \"Manager\" group and are set as \"Managers\" in <b><u>all</u></b> related servers.\n"
|
||||||
|
" This is done to avoid unpredictable consequences when some of the servers are not updated due to access restrictions when a project is updated."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Manageri.</b> Svi korisnici koji imaju \"Manager\" grupu i postavljeni su "
|
||||||
|
"kao \"Manageri\" u <b><u>svim</u></b> povezanim serverima.\n"
|
||||||
|
" "
|
||||||
|
"Ovo je napravljeno kako bi izbjegli nepredviđene posljedice kad neki od "
|
||||||
|
"servera nisu ažurirani zbog ograničenog pristupa prilikom ažuriranja "
|
||||||
|
"projekta."
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid ""
|
||||||
|
"<b>Users.</b> All users who have \"Manager\" group and are either set in "
|
||||||
|
"\"Users\" or in \"Managers\" in <b><u>all</u></b> related servers."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Korisnici.</b> Svi korisnici koji imaju \"Manager\" grupu i postavljeni "
|
||||||
|
"su ili kao \"Korisnik\" ili kao \"Manager\" u <b><u>svim</u></b> povezanim "
|
||||||
|
"serverima."
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "Access"
|
||||||
|
msgstr "Pristup"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__active
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__active
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__active
|
||||||
|
msgid "Active"
|
||||||
|
msgstr "Aktivno"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "Archived"
|
||||||
|
msgstr "Arhivirano"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__bitbucket
|
||||||
|
msgid "Bitbucket"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__branch
|
||||||
|
msgid "Branch"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form
|
||||||
|
msgid "Branch/PR/commit number or link"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__reference
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__reference
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__reference
|
||||||
|
msgid ""
|
||||||
|
"Can contain English letters, digits and '_'. Leave blank to autogenerate"
|
||||||
|
msgstr ""
|
||||||
|
"Može sadržavati slova engleske abecede, brojke i ':'. Ostavite prazno za "
|
||||||
|
"automatsko generiranje"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project
|
||||||
|
msgid "Cetmix Tower Git Configuration"
|
||||||
|
msgstr "Cetmix Tower Git postavke"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_remote
|
||||||
|
msgid "Cetmix Tower Git Remote"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_source
|
||||||
|
msgid "Cetmix Tower Git Source"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_server
|
||||||
|
msgid "Cetmix Tower Server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Code generator function for '%(project_format)s' format not found."
|
||||||
|
msgstr ""
|
||||||
|
"Funkcija generiranja koda za '%(project_format)s' format nije pronađena."
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__commit
|
||||||
|
msgid "Commit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_uid
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_uid
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_uid
|
||||||
|
msgid "Created by"
|
||||||
|
msgstr "Kreirao"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__create_date
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__create_date
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__create_date
|
||||||
|
msgid "Created on"
|
||||||
|
msgstr "Kreirano"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_file
|
||||||
|
msgid "Cx Tower File"
|
||||||
|
msgstr "Cx Tower datoteka"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid "Disabled"
|
||||||
|
msgstr "Onemogućen"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__display_name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__display_name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__display_name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__display_name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__display_name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__display_name
|
||||||
|
msgid "Display Name"
|
||||||
|
msgstr "Naziv"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__enabled
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_source__enabled
|
||||||
|
msgid "Enable in configuration and exported to files"
|
||||||
|
msgstr "Omogućen u postavkama i izvezen u datoteku"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__enabled
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__enabled
|
||||||
|
msgid "Enabled"
|
||||||
|
msgstr "Omogućen"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "Export YAML"
|
||||||
|
msgstr "Izvoz YAML"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__file_id
|
||||||
|
msgid "File"
|
||||||
|
msgstr "Datoteka"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_project_rel.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "File '%(file)s' doesn't belong to server '%(server)s'"
|
||||||
|
msgstr "Datoteka '%(file)s' ne pripada serveru '%(server)s'"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_file.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"File '%(file)s' is related to multiple projects: %(projects)s \n"
|
||||||
|
"Please select only one project."
|
||||||
|
msgstr ""
|
||||||
|
"Datoteka '%(file)s' je povezana sa višestrukim projektima: %(projects)s \n"
|
||||||
|
"Molim odaberite samo jedan projekt."
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.constraint,message:cetmix_tower_git.constraint_cx_tower_git_project_rel_project_server_file_format_uniq
|
||||||
|
msgid "File is already related to the same project and format"
|
||||||
|
msgstr "Datoteka je već povezana sa ovim projektom i ovim formatom"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__file_ids
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "Files"
|
||||||
|
msgstr "Datoteke"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__project_format
|
||||||
|
msgid "Format"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir
|
||||||
|
msgid "Git Aggregator Root Dir"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Git Aggregator: Bitbucket does not support fetching PRs. Please use branch instead.\n"
|
||||||
|
"\n"
|
||||||
|
"Source: %(src)s\n"
|
||||||
|
"URL: %(url)s\n"
|
||||||
|
"Head: %(head)s"
|
||||||
|
msgstr ""
|
||||||
|
"Git Aggregator: BitBucket ne podržava dohvaćanje PRova. Molim koristite "
|
||||||
|
"branch.\n"
|
||||||
|
"\n"
|
||||||
|
"Source: %(src)s\n"
|
||||||
|
"URL: %(url)s\n"
|
||||||
|
"Head: %(head)s"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Git Aggregator: Head number is empty in %(head)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__git_project_id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__git_project_id
|
||||||
|
msgid "Git Configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__git_project_id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_ids
|
||||||
|
msgid "Git Project"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__git_project_rel_ids
|
||||||
|
msgid "Git Project Rel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__git_project_rel_ids
|
||||||
|
msgid "Git Project Server File Relations"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model,name:cetmix_tower_git.model_cx_tower_git_project_rel
|
||||||
|
msgid "Git Project relation to other model records"
|
||||||
|
msgstr "Git projekt povezan sa ostalim zapisima modela"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.actions.act_window,name:cetmix_tower_git.cx_tower_git_project_action
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__git_project_ids
|
||||||
|
#: model:ir.ui.menu,name:cetmix_tower_git.menu_cx_tower_git_project
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_server_view_form
|
||||||
|
msgid "Git Projects"
|
||||||
|
msgstr "Git projekti"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__git_aggregator_root_dir
|
||||||
|
msgid ""
|
||||||
|
"Git aggregator root directory where sources will be cloned. Eg '/tmp/git-"
|
||||||
|
"aggregator' Will use '.' if not set"
|
||||||
|
msgstr ""
|
||||||
|
"GitAgregator izvorni direktorij u koji će izvori biti klonirani. Npr. '/tmp/"
|
||||||
|
"git-aggregator' Ako ništa nije postavljeno koristi se '.'"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid ""
|
||||||
|
"Git aggregator root directory where sources will be cloned. Leave blank to "
|
||||||
|
"use '.'"
|
||||||
|
msgstr ""
|
||||||
|
"GitAgregtor izvorni dirketorij u koji će izvori biti klonirani. Ostavite "
|
||||||
|
"prazno za korištenje '.'"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__url
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_remote_view_form
|
||||||
|
msgid ""
|
||||||
|
"Git remote URL. Eg 'https://github.com/cetmix/cetmix-tower.git' or "
|
||||||
|
"'git@github.com:cetmix/cetmix-tower.git'"
|
||||||
|
msgstr ""
|
||||||
|
"Git remote URL. Eg 'https://github.com/cetmix/cetmix-tower.git' ili "
|
||||||
|
"'git@github.com:cetmix/cetmix-tower.git'"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__head
|
||||||
|
msgid ""
|
||||||
|
"Git remote head. Link to branch, PR, commit or commit hash. Leave blank to "
|
||||||
|
"auto-detect"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__github
|
||||||
|
msgid "GitHub"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__gitlab
|
||||||
|
msgid "GitLab"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__https
|
||||||
|
msgid "HTTPS"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes
|
||||||
|
msgid "Has Partially Private Remotes"
|
||||||
|
msgstr "Ima djelomično privatne udaljene izvore"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes
|
||||||
|
msgid "Has Private Remotes"
|
||||||
|
msgstr "Ima privatne udaljene izvore"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head
|
||||||
|
msgid "Head"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__head_type
|
||||||
|
msgid "Head Type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file__id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__id
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server__id
|
||||||
|
msgid "ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_partially_private_remotes
|
||||||
|
msgid "Indicates if the project has any partially private remotes."
|
||||||
|
msgstr "Indicira ima li projekt djelomično privatnih udaljenih izvora."
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__has_private_remotes
|
||||||
|
msgid "Indicates if the project has any private remotes."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__is_private
|
||||||
|
msgid "Is Private"
|
||||||
|
msgstr "Je privatno"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_file____last_update
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project____last_update
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel____last_update
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote____last_update
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source____last_update
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_server____last_update
|
||||||
|
msgid "Last Modified on"
|
||||||
|
msgstr "Zadnje modificirano"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_uid
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_uid
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_uid
|
||||||
|
msgid "Last Updated by"
|
||||||
|
msgstr "Zadnji ažurirao"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__write_date
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__write_date
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__write_date
|
||||||
|
msgid "Last Updated on"
|
||||||
|
msgstr "Zadnje ažurirano"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__manager_ids
|
||||||
|
msgid "Managers"
|
||||||
|
msgstr "Manageri"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__manager_ids
|
||||||
|
msgid "Managers who can modify this record"
|
||||||
|
msgstr "Manageri koji mogu urediti ovaj zapis"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__name
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__name
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Naziv"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Not a valid URL. URL must end with '.git'"
|
||||||
|
msgstr "Nije valjani URL. URL mora završavati sa '.git'"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Not a valid URL. URL must start with 'https://' or 'git@'"
|
||||||
|
msgstr "Nije valjani URL. URL mora počinjati sa 'https://' ili 'git@'"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Not a valid URL: %(url_msg)s\n"
|
||||||
|
"URL must contain at least two parts separated by dot."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__repo_provider__other
|
||||||
|
msgid "Other"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid "Private"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count_private
|
||||||
|
msgid "Private Remotes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__head_type__pr
|
||||||
|
msgid "Pull/Merge Request"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__reference
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__reference
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__reference
|
||||||
|
msgid "Reference"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid ""
|
||||||
|
"Reference. Can contain English letters, digits and '_'. Leave blank to "
|
||||||
|
"autogenerate"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_count
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__remote_ids
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid "Remotes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__repo_provider
|
||||||
|
msgid "Repository Provider"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__is_private
|
||||||
|
msgid "Repository is private"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields.selection,name:cetmix_tower_git.selection__cx_tower_git_remote__url_protocol__ssh
|
||||||
|
msgid "SSH"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__sequence
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__sequence
|
||||||
|
msgid "Sequence"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project_rel__server_id
|
||||||
|
msgid "Server"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__server_ids
|
||||||
|
msgid "Servers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__server_ids
|
||||||
|
msgid ""
|
||||||
|
"Servers are added automatically based on the files linked to the project.\n"
|
||||||
|
"IMPORTANT: This field may contain duplicates because of the relation nature!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__source_id
|
||||||
|
msgid "Source"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__source_ids
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "Sources"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_source_view_form
|
||||||
|
msgid ""
|
||||||
|
"The top one remote will be used as a merge target.\n"
|
||||||
|
" You can re-arrange remotes by dragging them or changing their sequence value."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__url
|
||||||
|
msgid "URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__url_protocol
|
||||||
|
msgid "URL Protocol"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: code:addons/cetmix_tower_git/models/cx_tower_git_remote.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "URL is required"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__user_ids
|
||||||
|
msgid "Users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_project__user_ids
|
||||||
|
msgid "Users who can view this record"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,help:cetmix_tower_git.field_cx_tower_git_remote__repo_provider
|
||||||
|
msgid ""
|
||||||
|
"Will be tried to be determined from the URL. Please select manually if auto-"
|
||||||
|
"detection fails."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "YAML"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_project__yaml_code
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_remote__yaml_code
|
||||||
|
#: model:ir.model.fields,field_description:cetmix_tower_git.field_cx_tower_git_source__yaml_code
|
||||||
|
msgid "Yaml Code"
|
||||||
|
msgstr "YAML kod"
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid ""
|
||||||
|
"You can edit these fields at your own risk. However keep in mind that they "
|
||||||
|
"will be automatically updated each time related servers are added, removed "
|
||||||
|
"or updated."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "You must be a member of the \"YAML/Export\" group to export data as YAML."
|
||||||
|
msgstr "Morate bit član \"YAML/Izvoz\" grupe za izvoz podataka u YAML."
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "managers who can modify this record"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: cetmix_tower_git
|
||||||
|
#: model_terms:ir.ui.view,arch_db:cetmix_tower_git.cx_tower_git_project_view_form
|
||||||
|
msgid "users who can view this record"
|
||||||
|
msgstr ""
|
||||||
1085
addons/cetmix_tower_git/i18n/it.po
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import SUPERUSER_ID, api
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(cr, version):
|
||||||
|
"""
|
||||||
|
Convert URLs in remotes to repositories.
|
||||||
|
Add repo_id to remotes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
"Converting URLs in remotes to repositories and adding repo_id to remotes."
|
||||||
|
)
|
||||||
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
|
|
||||||
|
# Fetch all remotes using SQL query Group them {"url": [remote_id, remote_id, ...]}
|
||||||
|
cr.execute(
|
||||||
|
"""
|
||||||
|
SELECT url, array_agg(id) as remote_ids
|
||||||
|
FROM cx_tower_git_remote
|
||||||
|
GROUP BY url
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
remote_urls = cr.fetchall()
|
||||||
|
remote_urls_dict = {url: remote_ids for url, remote_ids in remote_urls}
|
||||||
|
|
||||||
|
# Create repo for each url and add this repo to all remotes
|
||||||
|
url_count = 0
|
||||||
|
remote_obj = env["cx.tower.git.remote"]
|
||||||
|
repo_obj = env["cx.tower.git.repo"]
|
||||||
|
for url, remote_ids in remote_urls_dict.items():
|
||||||
|
repo_id = repo_obj.name_create(url)[0]
|
||||||
|
# Check if any of the remotes is private
|
||||||
|
remotes = remote_obj.browse(remote_ids)
|
||||||
|
is_private = bool(remotes.filtered(lambda r: r.is_private))
|
||||||
|
|
||||||
|
# Add repo to remotes
|
||||||
|
# We are using SQL to avoid post-write triggers
|
||||||
|
cr.execute(
|
||||||
|
"""
|
||||||
|
UPDATE cx_tower_git_remote
|
||||||
|
SET repo_id = %s
|
||||||
|
WHERE id = ANY(%s)
|
||||||
|
""",
|
||||||
|
(repo_id, remote_ids),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update repo.is_private
|
||||||
|
# We are using SQL to avoid post-write triggers
|
||||||
|
if is_private:
|
||||||
|
cr.execute(
|
||||||
|
"""
|
||||||
|
UPDATE cx_tower_git_repo
|
||||||
|
SET is_private = true
|
||||||
|
WHERE id = %s
|
||||||
|
""",
|
||||||
|
(repo_id,),
|
||||||
|
)
|
||||||
|
|
||||||
|
url_count += 1
|
||||||
|
|
||||||
|
# Compute project_ids for repositories
|
||||||
|
_logger.info("Computing project_ids for repositories.")
|
||||||
|
remote_obj.invalidate_model()
|
||||||
|
repo_obj.invalidate_model()
|
||||||
|
repo_obj.search([])._compute_git_project_ids()
|
||||||
|
|
||||||
|
# Sanitize all remote heads that contain a slash
|
||||||
|
# Use the SQL query to avoid post-write triggers
|
||||||
|
_logger.info("Sanitizing remote heads that contain a slash.")
|
||||||
|
cr.execute(
|
||||||
|
"""
|
||||||
|
UPDATE cx_tower_git_remote
|
||||||
|
SET head = (regexp_match(head, '[^/]+$'))[1]
|
||||||
|
WHERE head LIKE '%/%'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
_logger.info("Migration completed. %s unique urls processed", url_count)
|
||||||
15
addons/cetmix_tower_git/models/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# cx_tower_git_project_rel must be the first one in the list
|
||||||
|
# in order to create the relation table properly
|
||||||
|
from . import cx_tower_git_project_rel
|
||||||
|
from . import cx_tower_git_project_file_template_rel
|
||||||
|
from . import cx_tower_file
|
||||||
|
from . import cx_tower_file_template
|
||||||
|
from . import cx_tower_git_project
|
||||||
|
from . import cx_tower_git_remote
|
||||||
|
from . import cx_tower_git_repo
|
||||||
|
from . import cx_tower_git_repo_owner
|
||||||
|
from . import cx_tower_git_source
|
||||||
|
from . import cx_tower_server
|
||||||
|
from . import cetmix_tower
|
||||||
|
from . import cx_tower_plan_line
|
||||||
|
from . import cx_tower_command
|
||||||
35
addons/cetmix_tower_git/models/cetmix_tower.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import api, models
|
||||||
|
|
||||||
|
|
||||||
|
class CetmixTower(models.AbstractModel):
|
||||||
|
_inherit = "cetmix.tower"
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def servers_by_git_ref(self, repository_url, head=None, head_type=None):
|
||||||
|
"""
|
||||||
|
Return servers linked to a given Git repository reference.
|
||||||
|
|
||||||
|
This is a thin shortcut that delegates to
|
||||||
|
:meth:`cx.tower.server.get_servers_by_git_ref`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
repository_url : str
|
||||||
|
Pre-normalized canonical Git URL
|
||||||
|
(e.g. ``https://host/owner/repo.git``).
|
||||||
|
head : str, optional
|
||||||
|
Branch name, commit SHA, or PR identifier.
|
||||||
|
head_type : {'branch', 'commit', 'pr'}, optional
|
||||||
|
Type of the ``head`` argument.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
recordset of cx.tower.server
|
||||||
|
Matching servers. Empty recordset if no matches.
|
||||||
|
"""
|
||||||
|
return self.env["cx.tower.server"].get_servers_by_git_ref(
|
||||||
|
repository_url, head, head_type
|
||||||
|
)
|
||||||
37
addons/cetmix_tower_git/models/cx_tower_command.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Copyright 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import _, models
|
||||||
|
from odoo.tools.safe_eval import wrap_module
|
||||||
|
|
||||||
|
# Wrap giturlparse safely
|
||||||
|
giturlparse = wrap_module(__import__("giturlparse"), ["parse", "validate"])
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerCommand(models.Model):
|
||||||
|
"""Extends cx.tower.command to add giturlparse functionality."""
|
||||||
|
|
||||||
|
_inherit = "cx.tower.command"
|
||||||
|
|
||||||
|
def _custom_python_libraries(self):
|
||||||
|
"""
|
||||||
|
Add the giturlparse library to the available libraries.
|
||||||
|
"""
|
||||||
|
custom_python_libraries = super()._custom_python_libraries()
|
||||||
|
custom_python_libraries.update(
|
||||||
|
{
|
||||||
|
"cetmix_tower_git": {
|
||||||
|
"giturlparse": {
|
||||||
|
"import": giturlparse,
|
||||||
|
"help": _(
|
||||||
|
"Python library for Git URL parsing. "
|
||||||
|
"Available methods: 'parse', 'validate'. "
|
||||||
|
" <a "
|
||||||
|
"href='https://github.com/nephila/giturlparse/'"
|
||||||
|
" target='_blank'>Documentation on GitHub</a>."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return custom_python_libraries
|
||||||
47
addons/cetmix_tower_git/models/cx_tower_file.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerFile(models.Model):
|
||||||
|
_inherit = "cx.tower.file"
|
||||||
|
|
||||||
|
git_project_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.git.project",
|
||||||
|
compute="_compute_git_project_id",
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
|
git_project_rel_ids = fields.One2many(
|
||||||
|
comodel_name="cx.tower.git.project.rel",
|
||||||
|
inverse_name="file_id",
|
||||||
|
string="Git Project Relations",
|
||||||
|
copy=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get server from the first related git project relation
|
||||||
|
# This is needed for YAML import
|
||||||
|
server_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.server",
|
||||||
|
compute="_compute_git_project_id",
|
||||||
|
store=True,
|
||||||
|
readonly=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("git_project_rel_ids.server_id", "git_project_rel_ids.git_project_id")
|
||||||
|
def _compute_git_project_id(self):
|
||||||
|
"""
|
||||||
|
Link to project using the proxy model.
|
||||||
|
"""
|
||||||
|
for record in self:
|
||||||
|
# File is related to project via proxy model.
|
||||||
|
# So there can be only one record in o2m field.
|
||||||
|
git_project_relation = (
|
||||||
|
record.git_project_rel_ids and record.git_project_rel_ids[0]
|
||||||
|
)
|
||||||
|
if git_project_relation:
|
||||||
|
record.update(
|
||||||
|
{
|
||||||
|
"git_project_id": git_project_relation.git_project_id,
|
||||||
|
"server_id": git_project_relation.server_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
32
addons/cetmix_tower_git/models/cx_tower_file_template.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerFileTemplate(models.Model):
|
||||||
|
_inherit = "cx.tower.file.template"
|
||||||
|
|
||||||
|
git_project_ids = fields.Many2many(
|
||||||
|
comodel_name="cx.tower.git.project",
|
||||||
|
relation="cx_tower_git_project_file_template_rel",
|
||||||
|
column1="file_template_id",
|
||||||
|
column2="git_project_id",
|
||||||
|
string="Git Projects",
|
||||||
|
copy=False,
|
||||||
|
)
|
||||||
|
git_project_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.git.project",
|
||||||
|
compute="_compute_git_project_id",
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("git_project_ids")
|
||||||
|
def _compute_git_project_id(self):
|
||||||
|
"""
|
||||||
|
Link to project using the proxy model.
|
||||||
|
"""
|
||||||
|
for record in self:
|
||||||
|
# File is related to project via proxy model.
|
||||||
|
# So there can be only one record in o2m field.
|
||||||
|
record.git_project_id = (
|
||||||
|
record.git_project_ids and record.git_project_ids[0].id
|
||||||
|
)
|
||||||
334
addons/cetmix_tower_git/models/cx_tower_git_project.py
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerGitProject(models.Model):
|
||||||
|
"""
|
||||||
|
Git Project.
|
||||||
|
Implements pre-defined git configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cx.tower.git.project"
|
||||||
|
_description = "Cetmix Tower Git Project"
|
||||||
|
_order = "name"
|
||||||
|
|
||||||
|
_inherit = [
|
||||||
|
"cx.tower.reference.mixin",
|
||||||
|
"cx.tower.yaml.mixin",
|
||||||
|
"cx.tower.access.role.mixin",
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_post_create_fields(self):
|
||||||
|
res = super()._get_post_create_fields()
|
||||||
|
return res + [
|
||||||
|
"source_ids",
|
||||||
|
"git_project_rel_ids",
|
||||||
|
"git_project_file_template_rel_ids",
|
||||||
|
]
|
||||||
|
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
# IMPORTANT: This field may contain duplicates because of the relation nature!
|
||||||
|
server_ids = fields.Many2many(
|
||||||
|
comodel_name="cx.tower.server",
|
||||||
|
relation="cx_tower_git_project_rel",
|
||||||
|
column1="git_project_id",
|
||||||
|
column2="server_id",
|
||||||
|
string="Servers",
|
||||||
|
readonly=True,
|
||||||
|
copy=False,
|
||||||
|
help="Servers are added automatically based on the files linked to the project."
|
||||||
|
"\nIMPORTANT: This field may contain duplicates"
|
||||||
|
" because of the relation nature!",
|
||||||
|
)
|
||||||
|
source_ids = fields.One2many(
|
||||||
|
comodel_name="cx.tower.git.source",
|
||||||
|
inverse_name="git_project_id",
|
||||||
|
string="Sources",
|
||||||
|
auto_join=True,
|
||||||
|
copy=True,
|
||||||
|
)
|
||||||
|
git_project_rel_ids = fields.One2many(
|
||||||
|
comodel_name="cx.tower.git.project.rel",
|
||||||
|
inverse_name="git_project_id",
|
||||||
|
string="Git Project Server File Relations",
|
||||||
|
copy=False,
|
||||||
|
)
|
||||||
|
# Helper field to get all files related to git project
|
||||||
|
file_ids = fields.Many2many(
|
||||||
|
comodel_name="cx.tower.file",
|
||||||
|
relation="cx_tower_git_project_rel",
|
||||||
|
column1="git_project_id",
|
||||||
|
column2="file_id",
|
||||||
|
string="Files",
|
||||||
|
readonly=True,
|
||||||
|
depends=["git_project_rel_ids"],
|
||||||
|
copy=False,
|
||||||
|
)
|
||||||
|
git_project_file_template_rel_ids = fields.One2many(
|
||||||
|
comodel_name="cx.tower.git.project.file.template.rel",
|
||||||
|
inverse_name="git_project_id",
|
||||||
|
string="Git Project File Template Relations",
|
||||||
|
copy=False,
|
||||||
|
)
|
||||||
|
# Helper field to get all file templates related to git project
|
||||||
|
file_template_ids = fields.Many2many(
|
||||||
|
comodel_name="cx.tower.file.template",
|
||||||
|
relation="cx_tower_git_project_file_template_rel",
|
||||||
|
column1="git_project_id",
|
||||||
|
column2="file_template_id",
|
||||||
|
string="File Templates",
|
||||||
|
readonly=True,
|
||||||
|
depends=["git_project_file_template_rel_ids"],
|
||||||
|
copy=False,
|
||||||
|
)
|
||||||
|
# Helper field to get all repositories used in this project
|
||||||
|
repo_ids = fields.Many2many(
|
||||||
|
comodel_name="cx.tower.git.repo",
|
||||||
|
relation="cx_tower_git_repo_project_rel",
|
||||||
|
column1="project_id",
|
||||||
|
column2="repo_id",
|
||||||
|
string="Repositories",
|
||||||
|
readonly=True,
|
||||||
|
copy=False,
|
||||||
|
help="Repositories used in this project through its sources and remotes",
|
||||||
|
)
|
||||||
|
note = fields.Text()
|
||||||
|
|
||||||
|
# ---- Access. Add relation for mixin fields
|
||||||
|
user_ids = fields.Many2many(
|
||||||
|
relation="cx_tower_git_project_user_rel",
|
||||||
|
compute="_compute_user_ids",
|
||||||
|
readonly=False,
|
||||||
|
store=True,
|
||||||
|
precompute=True,
|
||||||
|
domain=lambda self: [
|
||||||
|
("groups_id", "in", self.env.ref("cetmix_tower_server.group_manager").ids)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
manager_ids = fields.Many2many(
|
||||||
|
relation="cx_tower_git_project_manager_rel",
|
||||||
|
compute="_compute_user_ids",
|
||||||
|
readonly=False,
|
||||||
|
store=True,
|
||||||
|
precompute=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- UI/UX fields
|
||||||
|
has_private_remotes = fields.Boolean(
|
||||||
|
compute="_compute_has_private_remotes",
|
||||||
|
help="Indicates if the project has any private remotes.",
|
||||||
|
)
|
||||||
|
has_partially_private_remotes = fields.Boolean(
|
||||||
|
compute="_compute_has_private_remotes",
|
||||||
|
help="Indicates if the project has any partially private remotes.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- Git Aggregator related fields
|
||||||
|
git_aggregator_root_dir = fields.Char(
|
||||||
|
help="Git aggregator root directory where sources will be cloned."
|
||||||
|
" Eg '/tmp/git-aggregator'"
|
||||||
|
" Will use '.' if not set",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _selection_project_format(self):
|
||||||
|
"""
|
||||||
|
Possible project formats.
|
||||||
|
Inherit and extend when adding new project formats.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of tuples: (code, name)
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
("git_aggregator", "Git Aggregator"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def _default_project_format(self):
|
||||||
|
"""
|
||||||
|
Default project format.
|
||||||
|
"""
|
||||||
|
return "git_aggregator"
|
||||||
|
|
||||||
|
@api.depends(
|
||||||
|
"git_project_rel_ids.server_id",
|
||||||
|
"git_project_rel_ids.server_id.user_ids",
|
||||||
|
"git_project_rel_ids.server_id.manager_ids",
|
||||||
|
)
|
||||||
|
def _compute_user_ids(self):
|
||||||
|
"""
|
||||||
|
Users. All users who have "Manager" group and are either set in "Users"
|
||||||
|
or in "Managers" in all related servers.
|
||||||
|
Managers. All users who have "Manager" group and are set as "Managers"
|
||||||
|
in all related servers.
|
||||||
|
|
||||||
|
This is done to avoid unpredictable consequences when some of the servers
|
||||||
|
are not updated due to access restrictions when a project is updated.
|
||||||
|
"""
|
||||||
|
for project in self:
|
||||||
|
# Do not compute if no servers are related
|
||||||
|
server_ids = project.git_project_rel_ids.server_id
|
||||||
|
if not server_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get all user and manager ids from related servers
|
||||||
|
all_user_ids = server_ids.user_ids.filtered(
|
||||||
|
lambda u: u.has_group("cetmix_tower_server.group_manager")
|
||||||
|
).ids
|
||||||
|
all_manager_ids = server_ids.manager_ids.ids
|
||||||
|
|
||||||
|
# Create a final list of user and manager ids
|
||||||
|
user_ids = []
|
||||||
|
manager_ids = []
|
||||||
|
# Check if user is present in all servers
|
||||||
|
for user_id in all_user_ids:
|
||||||
|
if all(
|
||||||
|
user_id in server.user_ids.ids or user_id in server.manager_ids.ids
|
||||||
|
for server in server_ids
|
||||||
|
):
|
||||||
|
user_ids.append(user_id)
|
||||||
|
# Check if manager is present in all servers
|
||||||
|
for manager_id in all_manager_ids:
|
||||||
|
if all(manager_id in server.manager_ids.ids for server in server_ids):
|
||||||
|
manager_ids.append(manager_id)
|
||||||
|
|
||||||
|
# Set the final lists
|
||||||
|
project.update(
|
||||||
|
{
|
||||||
|
"user_ids": [(6, 0, user_ids)],
|
||||||
|
"manager_ids": [(6, 0, manager_ids)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends(
|
||||||
|
"source_ids", "source_ids.remote_ids", "source_ids.remote_ids.is_private"
|
||||||
|
)
|
||||||
|
def _compute_has_private_remotes(self):
|
||||||
|
for project in self:
|
||||||
|
project.has_private_remotes = any(
|
||||||
|
source.remote_count > 0
|
||||||
|
and source.remote_count_private == source.remote_count
|
||||||
|
for source in project.source_ids
|
||||||
|
)
|
||||||
|
project.has_partially_private_remotes = any(
|
||||||
|
source.remote_count_private > 0
|
||||||
|
and source.remote_count_private != source.remote_count
|
||||||
|
for source in project.source_ids
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
res = super().create(vals_list)
|
||||||
|
# Update related files and templates on create
|
||||||
|
res._update_related_files_and_templates()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
res = super().write(vals)
|
||||||
|
# Update related files and templates on update
|
||||||
|
self._update_related_files_and_templates()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _update_related_files_and_templates(self):
|
||||||
|
# Update related files and templates
|
||||||
|
if self.git_project_rel_ids:
|
||||||
|
self.git_project_rel_ids._save_to_file()
|
||||||
|
if self.git_project_file_template_rel_ids:
|
||||||
|
self.git_project_file_template_rel_ids._save_to_file_template()
|
||||||
|
|
||||||
|
def _extract_variables_from_text(self, text):
|
||||||
|
"""Extract environment variables from text.
|
||||||
|
Helper method for file content generation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List: List of variables
|
||||||
|
"""
|
||||||
|
variables = re.findall(r"\$([A-Z0-9_]+)", text)
|
||||||
|
return sorted(list(set(variables)))
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# YAML mixin methods
|
||||||
|
# ------------------------------
|
||||||
|
def _get_fields_for_yaml(self):
|
||||||
|
res = super()._get_fields_for_yaml()
|
||||||
|
res += [
|
||||||
|
"name",
|
||||||
|
"note",
|
||||||
|
"source_ids",
|
||||||
|
"git_aggregator_root_dir",
|
||||||
|
]
|
||||||
|
return res
|
||||||
|
|
||||||
|
# -------------------------------
|
||||||
|
# Git Aggregator related methods
|
||||||
|
# -------------------------------
|
||||||
|
def _git_aggregator_prepare_record(self):
|
||||||
|
"""Prepare json structure for git aggregator.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: Json structure for git aggregator
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
values = {}
|
||||||
|
for source in self.source_ids:
|
||||||
|
if source.enabled and source.remote_count:
|
||||||
|
root_dir = self.git_aggregator_root_dir or "."
|
||||||
|
values.update(
|
||||||
|
{
|
||||||
|
f"/{source.reference}"
|
||||||
|
if root_dir == "/"
|
||||||
|
else f"{root_dir}/{source.reference}": source._git_aggregator_prepare_record() # noqa: E501
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return values
|
||||||
|
|
||||||
|
def _git_aggregator_prepare_yaml_comment(self, yaml_code):
|
||||||
|
"""Generate commentary for yaml file.
|
||||||
|
It includes brief instructions for git aggregator
|
||||||
|
and lists environment variables that are required.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
yaml_code (str): Yaml code
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: comment text or None
|
||||||
|
"""
|
||||||
|
|
||||||
|
comment_text = _(
|
||||||
|
"# This file is generated with Cetmix Tower https://cetmix.com/tower\n"
|
||||||
|
"# It's designed to be used with git-aggregator tool developed by Acsone.\n"
|
||||||
|
"# Documentation for git-aggregator: https://github.com/acsone/git-aggregator\n"
|
||||||
|
)
|
||||||
|
variable_list = self._extract_variables_from_text(yaml_code)
|
||||||
|
if variable_list:
|
||||||
|
comment_text += _(
|
||||||
|
"\n# You need to set the following variables in your environment:\n# %(vars)s\n" # noqa: E501
|
||||||
|
"# and run git-aggregator with '--expand-env' parameter.\n", # noqa: E501
|
||||||
|
vars=(", ".join(variable_list)),
|
||||||
|
)
|
||||||
|
return comment_text
|
||||||
|
|
||||||
|
def _generate_code_git_aggregator(self, record):
|
||||||
|
"""Generate code in git-aggregator format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
record (recordset()): Model record to generate code for.
|
||||||
|
must be a single record and have git_project_id field.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Text: Yaml code
|
||||||
|
"""
|
||||||
|
yaml_mixin = self.env["cx.tower.yaml.mixin"]
|
||||||
|
|
||||||
|
# Do not generate code if record values are empty
|
||||||
|
record_values = record.git_project_id._git_aggregator_prepare_record()
|
||||||
|
if record_values:
|
||||||
|
yaml_code = yaml_mixin._convert_dict_to_yaml(record_values)
|
||||||
|
# Prepend comment to yaml code
|
||||||
|
comment = record.git_project_id._git_aggregator_prepare_yaml_comment(
|
||||||
|
yaml_code
|
||||||
|
)
|
||||||
|
return f"{comment}\n{yaml_code}"
|
||||||
|
return ""
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerGitProjectFileTemplateRel(models.Model):
|
||||||
|
"""
|
||||||
|
Relation between git projects and file templates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cx.tower.git.project.file.template.rel"
|
||||||
|
_table = "cx_tower_git_project_file_template_rel"
|
||||||
|
_description = "Cetmix Tower Git Project relation to File Templates"
|
||||||
|
_log_access = False
|
||||||
|
|
||||||
|
name = fields.Char(related="git_project_id.name", readonly=True)
|
||||||
|
git_project_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.git.project",
|
||||||
|
index=True,
|
||||||
|
required=True,
|
||||||
|
ondelete="cascade",
|
||||||
|
)
|
||||||
|
file_template_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.file.template",
|
||||||
|
required=True,
|
||||||
|
ondelete="cascade",
|
||||||
|
)
|
||||||
|
project_format = fields.Selection(
|
||||||
|
selection=lambda self: self.env[
|
||||||
|
"cx.tower.git.project"
|
||||||
|
]._selection_project_format(),
|
||||||
|
default=lambda self: self.env["cx.tower.git.project"]._default_project_format(),
|
||||||
|
required=True,
|
||||||
|
string="Format",
|
||||||
|
)
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
(
|
||||||
|
"project_server_file_format_uniq",
|
||||||
|
"unique(git_project_id, file_template_id, project_format)",
|
||||||
|
"File template is already related to the same project and format",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
res = super().create(vals_list)
|
||||||
|
|
||||||
|
# Export project to file
|
||||||
|
res._save_to_file_template()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
res = super().write(vals)
|
||||||
|
# Export project to file
|
||||||
|
self._save_to_file_template()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def action_open_file_template(self):
|
||||||
|
"""
|
||||||
|
Open file template record in current window
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"name": self.file_template_id.name,
|
||||||
|
"res_model": "cx.tower.file.template",
|
||||||
|
"res_id": self.file_template_id.id, # pylint: disable=no-member
|
||||||
|
"view_mode": "form",
|
||||||
|
"view_type": "form",
|
||||||
|
"target": "current",
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# Save project to linked file based on selected format
|
||||||
|
# ----------------------------------------------------
|
||||||
|
def _save_to_file_template(self):
|
||||||
|
"""Save project to linked file using format-specific function."""
|
||||||
|
|
||||||
|
# Get required function based on project format
|
||||||
|
# Following the pattern: _generate_code__<format> where format
|
||||||
|
# is one of the values in _selection_project_format
|
||||||
|
# Function gets a single record as an argument.
|
||||||
|
|
||||||
|
# Save resolved functions to dict for faster access
|
||||||
|
code_generator_functions = {}
|
||||||
|
|
||||||
|
for record in self:
|
||||||
|
code_generator_function = code_generator_functions.get(
|
||||||
|
record.project_format
|
||||||
|
)
|
||||||
|
if not code_generator_function:
|
||||||
|
code_generator_function = getattr(
|
||||||
|
self.git_project_id, f"_generate_code_{record.project_format}", None
|
||||||
|
)
|
||||||
|
if not code_generator_function:
|
||||||
|
raise ValidationError(
|
||||||
|
_(
|
||||||
|
"Code generator function for '%(project_format)s'"
|
||||||
|
" format not found.",
|
||||||
|
project_format=record.project_format,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
code_generator_functions[
|
||||||
|
record.project_format
|
||||||
|
] = code_generator_function
|
||||||
|
|
||||||
|
# Generate code for current record
|
||||||
|
code = code_generator_function(record)
|
||||||
|
if record.file_template_id.code != code:
|
||||||
|
record.file_template_id.write({"code": code})
|
||||||
177
addons/cetmix_tower_git/models/cx_tower_git_project_rel.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerGitProjectRel(models.Model):
|
||||||
|
"""
|
||||||
|
Relation between git projects and other model records.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cx.tower.git.project.rel"
|
||||||
|
_inherit = [
|
||||||
|
"cx.tower.reference.mixin",
|
||||||
|
"cx.tower.yaml.mixin",
|
||||||
|
]
|
||||||
|
_table = "cx_tower_git_project_rel"
|
||||||
|
_description = "Cetmix Tower Git Project relation to Files and Servers"
|
||||||
|
_log_access = False
|
||||||
|
|
||||||
|
name = fields.Char(related="git_project_id.name", readonly=True)
|
||||||
|
git_project_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.git.project",
|
||||||
|
index=True,
|
||||||
|
required=True,
|
||||||
|
ondelete="cascade",
|
||||||
|
)
|
||||||
|
server_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.server",
|
||||||
|
index=True,
|
||||||
|
required=True,
|
||||||
|
ondelete="cascade",
|
||||||
|
)
|
||||||
|
file_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.file",
|
||||||
|
domain="[('server_id', '=', server_id),"
|
||||||
|
"('source', '=', 'tower'),"
|
||||||
|
"('file_type', '=', 'text')]",
|
||||||
|
required=True,
|
||||||
|
ondelete="cascade",
|
||||||
|
)
|
||||||
|
project_format = fields.Selection(
|
||||||
|
selection=lambda self: self.env[
|
||||||
|
"cx.tower.git.project"
|
||||||
|
]._selection_project_format(),
|
||||||
|
default=lambda self: self.env["cx.tower.git.project"]._default_project_format(),
|
||||||
|
required=True,
|
||||||
|
string="Format",
|
||||||
|
)
|
||||||
|
auto_sync = fields.Boolean(related="file_id.auto_sync", readonly=False)
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
(
|
||||||
|
"project_server_file_format_uniq",
|
||||||
|
"unique(git_project_id, file_id, project_format)",
|
||||||
|
"File is already related to the same project and format",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
@api.constrains("server_id", "file_id")
|
||||||
|
def _check_server_file_relation(self):
|
||||||
|
"""
|
||||||
|
Check if server and file are related.
|
||||||
|
"""
|
||||||
|
for record in self:
|
||||||
|
if (
|
||||||
|
record.file_id.server_id
|
||||||
|
and record.server_id != record.file_id.server_id
|
||||||
|
):
|
||||||
|
raise ValidationError(
|
||||||
|
_(
|
||||||
|
"File '%(file)s' doesn't belong to server '%(server)s'",
|
||||||
|
file=record.file_id.name,
|
||||||
|
server=record.server_id.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
res = super().create(vals_list)
|
||||||
|
|
||||||
|
# Export project to file
|
||||||
|
res._save_to_file()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
res = super().write(vals)
|
||||||
|
# Export project to file
|
||||||
|
self._save_to_file()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def action_open_project(self):
|
||||||
|
"""
|
||||||
|
Open project record in current window
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"name": self.name,
|
||||||
|
"res_model": "cx.tower.git.project",
|
||||||
|
"res_id": self.git_project_id.id, # pylint: disable=no-member
|
||||||
|
"view_mode": "form",
|
||||||
|
"view_type": "form",
|
||||||
|
"target": "current",
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_open_server(self):
|
||||||
|
"""
|
||||||
|
Open server record in current window
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"name": self.server_id.name,
|
||||||
|
"res_model": "cx.tower.server",
|
||||||
|
"res_id": self.server_id.id, # pylint: disable=no-member
|
||||||
|
"view_mode": "form",
|
||||||
|
"view_type": "form",
|
||||||
|
"target": "current",
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------
|
||||||
|
# Save project to linked file based on selected format
|
||||||
|
# ----------------------------------------------------
|
||||||
|
def _save_to_file(self):
|
||||||
|
"""Save project to linked file using format-specific function."""
|
||||||
|
|
||||||
|
# Get required function based on project format
|
||||||
|
# Following the pattern: _generate_code_<format> where format
|
||||||
|
# is one of the values in _selection_project_format
|
||||||
|
# Function gets a single record as an argument.
|
||||||
|
|
||||||
|
# Save resolved functions to dict for faster access
|
||||||
|
code_generator_functions = {}
|
||||||
|
|
||||||
|
for record in self:
|
||||||
|
# Disconnect file from file template if it is connected
|
||||||
|
if record.file_id.template_id:
|
||||||
|
record.file_id.action_unlink_from_template()
|
||||||
|
|
||||||
|
code_generator_function = code_generator_functions.get(
|
||||||
|
record.project_format
|
||||||
|
)
|
||||||
|
if not code_generator_function:
|
||||||
|
code_generator_function = getattr(
|
||||||
|
self.git_project_id, f"_generate_code_{record.project_format}", None
|
||||||
|
)
|
||||||
|
if not code_generator_function:
|
||||||
|
raise ValidationError(
|
||||||
|
_(
|
||||||
|
"Code generator function for '%(project_format)s'"
|
||||||
|
" format not found.",
|
||||||
|
project_format=record.project_format,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
code_generator_functions[
|
||||||
|
record.project_format
|
||||||
|
] = code_generator_function
|
||||||
|
|
||||||
|
# Generate code for current record
|
||||||
|
code = code_generator_function(record)
|
||||||
|
if record.file_id.code != code:
|
||||||
|
record.file_id.write({"code": code})
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# YAML mixin methods
|
||||||
|
# ------------------------------
|
||||||
|
def _get_fields_for_yaml(self):
|
||||||
|
res = super()._get_fields_for_yaml()
|
||||||
|
res += [
|
||||||
|
"file_id",
|
||||||
|
"git_project_id",
|
||||||
|
"project_format",
|
||||||
|
"auto_sync",
|
||||||
|
]
|
||||||
|
return res
|
||||||
415
addons/cetmix_tower_git/models/cx_tower_git_remote.py
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
import giturlparse
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerGitRemote(models.Model):
|
||||||
|
"""
|
||||||
|
Git Remote.
|
||||||
|
Implements single git remote.
|
||||||
|
Eg a branch or a pull request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cx.tower.git.remote"
|
||||||
|
_inherit = [
|
||||||
|
"cx.tower.reference.mixin",
|
||||||
|
"cx.tower.yaml.mixin",
|
||||||
|
]
|
||||||
|
_description = "Cetmix Tower Git Remote"
|
||||||
|
_order = "sequence, name"
|
||||||
|
|
||||||
|
# Used to detect git ssh urls
|
||||||
|
GIT_SSH_URL_PATTERN = r"^[\w\.-]+@[\w\.-]+:.*\.git$"
|
||||||
|
GIT_HTTPS_URL_PATTERN = r"^https://.*\.git$"
|
||||||
|
GIT_GIT_URL_PATTERN = r"^git://.*\.git$"
|
||||||
|
|
||||||
|
active = fields.Boolean(related="source_id.active", store=True, readonly=True)
|
||||||
|
enabled = fields.Boolean(
|
||||||
|
default=True, help="Enable in configuration and exported to files"
|
||||||
|
)
|
||||||
|
sequence = fields.Integer(default=10)
|
||||||
|
name = fields.Char(compute="_compute_name", store=True, default="remote")
|
||||||
|
source_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.git.source",
|
||||||
|
required=True,
|
||||||
|
ondelete="cascade",
|
||||||
|
auto_join=True,
|
||||||
|
)
|
||||||
|
git_project_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.git.project",
|
||||||
|
related="source_id.git_project_id",
|
||||||
|
store=True,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
repo_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.git.repo",
|
||||||
|
string="Repository",
|
||||||
|
required=True,
|
||||||
|
ondelete="restrict",
|
||||||
|
help="If selected, the remote URL will be filled from the"
|
||||||
|
" repo settings based on the remote protocol",
|
||||||
|
)
|
||||||
|
repo_provider = fields.Selection(
|
||||||
|
related="repo_id.provider",
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
# -- Repo related fields
|
||||||
|
url_protocol = fields.Selection(
|
||||||
|
string="Protocol",
|
||||||
|
selection=[
|
||||||
|
("ssh", "SSH"),
|
||||||
|
("https", "HTTPS"),
|
||||||
|
("git", "GIT"),
|
||||||
|
],
|
||||||
|
required=True,
|
||||||
|
default=lambda self: self._get_default_url_protocol(),
|
||||||
|
)
|
||||||
|
is_private = fields.Boolean(
|
||||||
|
string="Private",
|
||||||
|
help="Repository is private",
|
||||||
|
related="repo_id.is_private",
|
||||||
|
store=True,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
head_type = fields.Selection(
|
||||||
|
selection=[
|
||||||
|
("branch", "Branch"),
|
||||||
|
("pr", "Pull/Merge Request"),
|
||||||
|
("commit", "Commit"),
|
||||||
|
],
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
head = fields.Char(
|
||||||
|
help="Git remote head. Link to branch, PR, commit or commit hash.",
|
||||||
|
required=True,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_default_url_protocol(self):
|
||||||
|
"""Default URL protocol for new remote.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: Default URL protocol.
|
||||||
|
"""
|
||||||
|
return "https"
|
||||||
|
|
||||||
|
@api.depends("source_id", "sequence")
|
||||||
|
def _compute_name(self):
|
||||||
|
"""
|
||||||
|
Compute remote name.
|
||||||
|
By default all remotes are named `remote_<position>`
|
||||||
|
where position is the position of the remote in the source.
|
||||||
|
Eg first remote is `remote_1`, second is `remote_2`, etc.
|
||||||
|
"""
|
||||||
|
for remote in self:
|
||||||
|
if remote.source_id:
|
||||||
|
for index, source_remote in enumerate(remote.source_id.remote_ids):
|
||||||
|
source_remote.name = f"remote_{index + 1}"
|
||||||
|
|
||||||
|
@api.onchange("head")
|
||||||
|
def onchange_head(self):
|
||||||
|
"""
|
||||||
|
Extract head number from head url
|
||||||
|
and set it as head.
|
||||||
|
"""
|
||||||
|
for remote in self:
|
||||||
|
if remote.head and "/" in remote.head:
|
||||||
|
remote.head = self._sanitize_head(remote.head)
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
# Sanitize head
|
||||||
|
for vals in vals_list:
|
||||||
|
head = vals.get("head")
|
||||||
|
if head and "/" in head:
|
||||||
|
vals["head"] = self._sanitize_head(head)
|
||||||
|
res = super().create(vals_list)
|
||||||
|
# Export project to related files and templates
|
||||||
|
res._update_related_files_and_templates()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
# Sanitize head
|
||||||
|
if "head" in vals:
|
||||||
|
head = vals["head"]
|
||||||
|
if head and "/" in head:
|
||||||
|
vals["head"] = self._sanitize_head(head)
|
||||||
|
res = super().write(vals)
|
||||||
|
# Update related files and templates on update
|
||||||
|
self._update_related_files_and_templates()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
"""
|
||||||
|
Override to update related files and templates on unlink
|
||||||
|
"""
|
||||||
|
related_files = self.mapped("git_project_id").mapped("git_project_rel_ids")
|
||||||
|
related_templates = self.mapped("git_project_id").mapped(
|
||||||
|
"git_project_file_template_rel_ids"
|
||||||
|
)
|
||||||
|
res = super().unlink()
|
||||||
|
|
||||||
|
# Update related files and templates on unlink
|
||||||
|
if related_files:
|
||||||
|
related_files._save_to_file()
|
||||||
|
if related_templates:
|
||||||
|
related_templates._save_to_file_template()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _sanitize_head(self, head):
|
||||||
|
"""Sanitize head.
|
||||||
|
Extract head number from head url
|
||||||
|
and set it as head.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
head (Char): Head to sanitize
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: Sanitized head
|
||||||
|
"""
|
||||||
|
if head and "/" in head:
|
||||||
|
return head.split("/")[-1].strip()
|
||||||
|
return head
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_head_data(self):
|
||||||
|
"""
|
||||||
|
This method is used to get values for the dropdown dynamic widget.
|
||||||
|
It is designed for integrations with repo providers using APIs.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List: List of tuples(selection, name)
|
||||||
|
eg [('18.0', '18.0'), ('main', 'main'), ('develop', 'develop')]
|
||||||
|
"""
|
||||||
|
values = [
|
||||||
|
("18.0", "18.0"),
|
||||||
|
("main", "Main"),
|
||||||
|
("develop", "Develop"),
|
||||||
|
("17.0", "17.0"),
|
||||||
|
]
|
||||||
|
return values
|
||||||
|
|
||||||
|
def _update_related_files_and_templates(self):
|
||||||
|
# Update related files on update
|
||||||
|
related_files = self.mapped("git_project_id").mapped("git_project_rel_ids")
|
||||||
|
if related_files:
|
||||||
|
related_files._save_to_file()
|
||||||
|
related_templates = self.mapped("git_project_id").mapped(
|
||||||
|
"git_project_file_template_rel_ids"
|
||||||
|
)
|
||||||
|
if related_templates:
|
||||||
|
related_templates._save_to_file_template()
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Reference mixin methods
|
||||||
|
# ------------------------------
|
||||||
|
def _get_pre_populated_model_data(self):
|
||||||
|
res = super()._get_pre_populated_model_data()
|
||||||
|
res.update({"cx.tower.git.remote": ["cx.tower.git.source", "source_id"]})
|
||||||
|
return res
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# YAML mixin methods
|
||||||
|
# ------------------------------
|
||||||
|
def _get_fields_for_yaml(self):
|
||||||
|
res = super()._get_fields_for_yaml()
|
||||||
|
res += [
|
||||||
|
"name",
|
||||||
|
"enabled",
|
||||||
|
"sequence",
|
||||||
|
"repo_id",
|
||||||
|
"head",
|
||||||
|
"head_type",
|
||||||
|
]
|
||||||
|
return res
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Git Aggregator related methods
|
||||||
|
# ------------------------------
|
||||||
|
def _git_aggregator_prepare_url(self):
|
||||||
|
"""Prepare url for git aggregator
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: Prepared url for git aggregator
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
if not self.repo_id:
|
||||||
|
raise ValidationError(_("Repository is required"))
|
||||||
|
if not self.repo_id.url:
|
||||||
|
raise ValidationError(_("Repository URL is not set"))
|
||||||
|
|
||||||
|
url = self.repo_id.url
|
||||||
|
prepared_url = giturlparse.parse(url).urls.get(self.url_protocol, url)
|
||||||
|
|
||||||
|
# If repo is public or is not using HTTPS protocol return URL as is
|
||||||
|
if not self.is_private or self.url_protocol != "https":
|
||||||
|
return prepared_url
|
||||||
|
|
||||||
|
if self.repo_provider == "github":
|
||||||
|
prepared_url = self._git_aggregator_prepare_url_github(prepared_url)
|
||||||
|
elif self.repo_provider == "gitlab":
|
||||||
|
prepared_url = self._git_aggregator_prepare_url_gitlab(prepared_url)
|
||||||
|
elif self.repo_provider == "bitbucket":
|
||||||
|
prepared_url = self._git_aggregator_prepare_url_bitbucket(prepared_url)
|
||||||
|
|
||||||
|
return prepared_url
|
||||||
|
|
||||||
|
def _git_aggregator_prepare_url_github(self, url):
|
||||||
|
"""Prepare url for git aggregator
|
||||||
|
for private Github repo using https protocol.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (Char): URL to prepare
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: Prepared url for git aggregator
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
# This is how final url will look like
|
||||||
|
# https://$GITHUB_TOKEN:x-oauth-basic@github.com/soem_org/some_private_repo.git
|
||||||
|
url_without_protocol = url.replace("https://", "")
|
||||||
|
url = f"https://$GITHUB_TOKEN:x-oauth-basic@{url_without_protocol}"
|
||||||
|
return url
|
||||||
|
|
||||||
|
def _git_aggregator_prepare_url_gitlab(self, url):
|
||||||
|
"""Prepare url for git aggregator
|
||||||
|
for private GitLab repo using https protocol.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (Char): URL to prepare
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: Prepared url for git aggregator
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
# This is how final url will look like
|
||||||
|
# https://<token-name>:<token-value>@<gitlaburl-repository>.git
|
||||||
|
url_without_protocol = url.replace("https://", "")
|
||||||
|
url = f"https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@{url_without_protocol}"
|
||||||
|
return url
|
||||||
|
|
||||||
|
def _git_aggregator_prepare_url_bitbucket(self, url):
|
||||||
|
"""Prepare url for git aggregator
|
||||||
|
for private Github repo using https protocol.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (Char): URL to prepare
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: Prepared url for git aggregator
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
# This is how final url will look like
|
||||||
|
# https://x-token-auth:{access_token}@bitbucket.org/user/repo.git
|
||||||
|
# From https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/
|
||||||
|
url_without_protocol = url.replace("https://", "")
|
||||||
|
url = f"https://x-token-auth:$BITBUCKET_TOKEN@{url_without_protocol}"
|
||||||
|
return url
|
||||||
|
|
||||||
|
def _git_aggregator_prepare_head(self):
|
||||||
|
"""Prepare head for git aggregator
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: Prepared head for git aggregator
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
if self.repo_provider == "github":
|
||||||
|
return self._git_aggregator_prepare_head_github()
|
||||||
|
if self.repo_provider == "gitlab":
|
||||||
|
return self._git_aggregator_prepare_head_gitlab()
|
||||||
|
if self.repo_provider == "bitbucket":
|
||||||
|
return self._git_aggregator_prepare_head_bitbucket()
|
||||||
|
return self.head
|
||||||
|
|
||||||
|
def _git_aggregator_prepare_head_github(self):
|
||||||
|
"""Prepare head for git aggregator for Github.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: Prepared head for git aggregator
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Extract branch name, PR/MR or commit number from head
|
||||||
|
head_number = self.head.split("/")[-1]
|
||||||
|
if not head_number:
|
||||||
|
raise ValidationError(
|
||||||
|
_("Git Aggregator: " "Head number is empty in %(head)s", head=self.head)
|
||||||
|
)
|
||||||
|
|
||||||
|
# PR/MR
|
||||||
|
if self.head_type == "pr":
|
||||||
|
return f"refs/pull/{head_number}/head"
|
||||||
|
|
||||||
|
# Commit
|
||||||
|
if self.head_type in ["commit", "branch"]:
|
||||||
|
return f"{head_number}"
|
||||||
|
|
||||||
|
# Fallback to original head
|
||||||
|
return self.head
|
||||||
|
|
||||||
|
def _git_aggregator_prepare_head_gitlab(self):
|
||||||
|
"""Prepare head for git aggregator for GitLab.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: Prepared head for git aggregator
|
||||||
|
"""
|
||||||
|
# Extract branch name, PR/MR or commit number from head
|
||||||
|
head_number = self.head.split("/")[-1]
|
||||||
|
if not head_number:
|
||||||
|
raise ValidationError(
|
||||||
|
_("Git Aggregator: " "Head number is empty in %(head)s", head=self.head)
|
||||||
|
)
|
||||||
|
|
||||||
|
# PR/MR
|
||||||
|
if self.head_type == "pr":
|
||||||
|
return f"merge-requests/{head_number}/head"
|
||||||
|
|
||||||
|
# Commit
|
||||||
|
# https://gitlab.com/cetmix/test/-/tree/17.0-test-branch?ref_type=heads
|
||||||
|
if self.head_type in ["commit", "branch"]:
|
||||||
|
head_parts = head_number.split("?")
|
||||||
|
return f"{head_parts[0]}"
|
||||||
|
|
||||||
|
# Fallback to original head
|
||||||
|
return self.head
|
||||||
|
|
||||||
|
def _git_aggregator_prepare_head_bitbucket(self):
|
||||||
|
"""Prepare head for git aggregator for Bitbucket.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Char: Prepared head for git aggregator
|
||||||
|
"""
|
||||||
|
# Extract branch name, PR/MR or commit number from head
|
||||||
|
head_number = self.head.split("/")[-1]
|
||||||
|
if not head_number:
|
||||||
|
raise ValidationError(
|
||||||
|
_("Git Aggregator: " "Head number is empty in %(head)s", head=self.head)
|
||||||
|
)
|
||||||
|
# PR/MR
|
||||||
|
if self.head_type == "pr":
|
||||||
|
raise ValidationError(
|
||||||
|
_(
|
||||||
|
"Git Aggregator: "
|
||||||
|
"Bitbucket does not support"
|
||||||
|
" fetching PRs. Please use branch instead.\n\n"
|
||||||
|
"Source: %(src)s\n"
|
||||||
|
"URL: %(url)s\n"
|
||||||
|
"Head: %(head)s",
|
||||||
|
src=self.source_id.name,
|
||||||
|
url=self.repo_id.url,
|
||||||
|
head=self.head,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Commit
|
||||||
|
if self.head_type in ["commit", "branch"]:
|
||||||
|
return f"{head_number}"
|
||||||
|
|
||||||
|
# Fallback to original head
|
||||||
|
return self.head
|
||||||
409
addons/cetmix_tower_git/models/cx_tower_git_repo.py
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
import giturlparse
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
from odoo.tools import ormcache
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerGitRepo(models.Model):
|
||||||
|
"""
|
||||||
|
Git Repository.
|
||||||
|
Represents a git repository with its metadata and configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cx.tower.git.repo"
|
||||||
|
_inherit = [
|
||||||
|
"cx.tower.reference.mixin",
|
||||||
|
"cx.tower.yaml.mixin",
|
||||||
|
]
|
||||||
|
_description = "Cetmix Tower Git Repository"
|
||||||
|
_order = "name"
|
||||||
|
_rec_names_search = ["repo", "host", "owner_id"]
|
||||||
|
|
||||||
|
active = fields.Boolean(default=True, help="Indicates if the repository is active")
|
||||||
|
name = fields.Char(
|
||||||
|
compute="_compute_name", store=True, required=False, index="trigram"
|
||||||
|
)
|
||||||
|
reference = fields.Char(
|
||||||
|
index=True,
|
||||||
|
compute="_compute_name",
|
||||||
|
required=False,
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
|
repo = fields.Char(
|
||||||
|
string="Repository Name",
|
||||||
|
readonly=True,
|
||||||
|
help="Repository name (e.g., 'cetmix-tower', 'odoo')",
|
||||||
|
)
|
||||||
|
url = fields.Char(
|
||||||
|
string="Generic URL",
|
||||||
|
help="Displayed in 'https' format, but can be entered in any format",
|
||||||
|
compute="_compute_url",
|
||||||
|
inverse="_inverse_url",
|
||||||
|
required=True,
|
||||||
|
compute_sudo=True,
|
||||||
|
)
|
||||||
|
url_ssh = fields.Char(
|
||||||
|
string="SSH URL",
|
||||||
|
help="SSH URL of the repository",
|
||||||
|
compute="_compute_url",
|
||||||
|
compute_sudo=True,
|
||||||
|
)
|
||||||
|
url_git = fields.Char(
|
||||||
|
string="GIT URL",
|
||||||
|
help="GIT URL of the repository",
|
||||||
|
compute="_compute_url",
|
||||||
|
compute_sudo=True,
|
||||||
|
)
|
||||||
|
is_private = fields.Boolean(
|
||||||
|
string="Private", default=False, help="Indicates if the repository is private"
|
||||||
|
)
|
||||||
|
provider = fields.Selection(
|
||||||
|
selection="_selection_provider",
|
||||||
|
required=True,
|
||||||
|
default="other",
|
||||||
|
help="Repository provider to determine provider-based behaviour",
|
||||||
|
)
|
||||||
|
host = fields.Char(
|
||||||
|
readonly=True,
|
||||||
|
index=True,
|
||||||
|
help="Repository host (e.g., 'github.com', 'gitlab.com')",
|
||||||
|
)
|
||||||
|
owner_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.git.repo.owner",
|
||||||
|
readonly=True,
|
||||||
|
help="Repository owner (e.g., 'cetmix' or 'OCA')",
|
||||||
|
)
|
||||||
|
secret_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.key",
|
||||||
|
string="Secret",
|
||||||
|
domain="[('key_type', '=', 's')]",
|
||||||
|
help="Custom secret used for this repository",
|
||||||
|
)
|
||||||
|
remote_ids = fields.One2many(
|
||||||
|
comodel_name="cx.tower.git.remote",
|
||||||
|
inverse_name="repo_id",
|
||||||
|
help="Remotes that use this repository",
|
||||||
|
)
|
||||||
|
git_project_ids = fields.Many2many(
|
||||||
|
comodel_name="cx.tower.git.project",
|
||||||
|
relation="cx_tower_git_repo_project_rel",
|
||||||
|
column1="repo_id",
|
||||||
|
column2="project_id",
|
||||||
|
compute="_compute_git_project_ids",
|
||||||
|
store=True,
|
||||||
|
help="Projects this repository is used in",
|
||||||
|
)
|
||||||
|
remote_count = fields.Integer(
|
||||||
|
compute="_compute_remote_count",
|
||||||
|
help="Number of remotes this repository is used in",
|
||||||
|
)
|
||||||
|
git_project_count = fields.Integer(
|
||||||
|
compute="_compute_git_project_count",
|
||||||
|
help="Number of projects this repository is used in",
|
||||||
|
)
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
(
|
||||||
|
"unique_repo_host_owner",
|
||||||
|
"unique(repo, host, owner_id)",
|
||||||
|
"A repository with the same name, host, and owner already exists.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# -- Selection
|
||||||
|
def _selection_provider(self):
|
||||||
|
"""Available repository providers.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of tuples: available options.
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
("github", "GitHub"),
|
||||||
|
("gitlab", "GitLab"),
|
||||||
|
("bitbucket", "Bitbucket"),
|
||||||
|
("assembla", "Assembla"),
|
||||||
|
("other", "Other"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# -- Computes
|
||||||
|
@api.depends("host", "owner_id", "repo")
|
||||||
|
def _compute_name(self):
|
||||||
|
"""
|
||||||
|
Compute name in format: host/owner/name.
|
||||||
|
Compute reference based on name.
|
||||||
|
"""
|
||||||
|
for repo in self:
|
||||||
|
if repo.host and repo.owner_id and repo.repo:
|
||||||
|
name = f"{repo.host}/{repo.owner_id.name}/{repo.repo}"
|
||||||
|
reference = repo._generate_or_fix_reference(name)
|
||||||
|
repo.update(
|
||||||
|
{
|
||||||
|
"name": name,
|
||||||
|
"reference": reference,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
repo.update(
|
||||||
|
{
|
||||||
|
"name": False,
|
||||||
|
"reference": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("remote_ids", "remote_ids.git_project_id")
|
||||||
|
def _compute_git_project_ids(self):
|
||||||
|
"""Compute projects this repository is used in."""
|
||||||
|
for repo in self:
|
||||||
|
projects = repo.remote_ids.mapped("git_project_id")
|
||||||
|
repo.git_project_ids = [(6, 0, projects.ids)]
|
||||||
|
|
||||||
|
@api.depends("remote_ids")
|
||||||
|
def _compute_remote_count(self):
|
||||||
|
"""Compute remote count field."""
|
||||||
|
for repo in self:
|
||||||
|
repo.remote_count = len(repo.remote_ids)
|
||||||
|
|
||||||
|
@api.depends("git_project_ids")
|
||||||
|
def _compute_git_project_count(self):
|
||||||
|
"""Compute project count field."""
|
||||||
|
for repo in self:
|
||||||
|
repo.git_project_count = len(repo.git_project_ids)
|
||||||
|
|
||||||
|
@api.depends("repo", "host", "owner_id")
|
||||||
|
def _compute_url(self):
|
||||||
|
"""Compute URL from repository properties."""
|
||||||
|
for repo in self:
|
||||||
|
if repo.repo and repo.host and repo.owner_id:
|
||||||
|
https_url = f"https://{repo.host}/{repo.owner_id.name}/{repo.repo}.git"
|
||||||
|
elif repo.repo and repo.host:
|
||||||
|
https_url = f"https://{repo.host}/{repo.repo}.git"
|
||||||
|
else:
|
||||||
|
https_url = ""
|
||||||
|
if https_url:
|
||||||
|
try:
|
||||||
|
parsed_urls = giturlparse.parse(https_url).urls
|
||||||
|
urls = {
|
||||||
|
"url": https_url,
|
||||||
|
"url_ssh": parsed_urls["ssh"],
|
||||||
|
"url_git": parsed_urls["git"],
|
||||||
|
}
|
||||||
|
except Exception as e: # noqa: F841 catch all errors
|
||||||
|
urls = {
|
||||||
|
"url": "",
|
||||||
|
"url_ssh": "",
|
||||||
|
"url_git": "",
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
urls = {
|
||||||
|
"url": "",
|
||||||
|
"url_ssh": "",
|
||||||
|
"url_git": "",
|
||||||
|
}
|
||||||
|
repo.update(urls)
|
||||||
|
|
||||||
|
def _inverse_url(self):
|
||||||
|
"""Parse URL to update repository properties."""
|
||||||
|
for repo in self:
|
||||||
|
if not repo.url:
|
||||||
|
continue
|
||||||
|
# Parse URL
|
||||||
|
parsed_url_dict = self._parse_url(repo.url)
|
||||||
|
# Update repository properties
|
||||||
|
repo.update(parsed_url_dict)
|
||||||
|
|
||||||
|
def action_view_remotes(self):
|
||||||
|
"""Open remotes list view."""
|
||||||
|
self.ensure_one()
|
||||||
|
action = self.env["ir.actions.actions"]._for_xml_id(
|
||||||
|
"cetmix_tower_git.action_cx_tower_git_remote"
|
||||||
|
)
|
||||||
|
action.update(
|
||||||
|
{
|
||||||
|
"domain": [("repo_id", "=", self.id)],
|
||||||
|
"context": {"default_repo_id": self.id},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return action
|
||||||
|
|
||||||
|
def action_view_projects(self):
|
||||||
|
"""Open projects list view."""
|
||||||
|
self.ensure_one()
|
||||||
|
action = self.env["ir.actions.actions"]._for_xml_id(
|
||||||
|
"cetmix_tower_git.cx_tower_git_project_action"
|
||||||
|
)
|
||||||
|
action.update(
|
||||||
|
{
|
||||||
|
"domain": [("repo_ids", "in", self.id)],
|
||||||
|
"context": {"default_repo_ids": [(4, self.id)]},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return action
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
"""Create multiple repositories."""
|
||||||
|
# Check if any of the repositories already exist
|
||||||
|
# This is needed to allow creating repositories using just an URL.
|
||||||
|
# Eg when importing repositories from a YAML file.
|
||||||
|
res = self.browse()
|
||||||
|
existing_repo_ids = []
|
||||||
|
vals_list_to_create = []
|
||||||
|
for vals in vals_list:
|
||||||
|
url = vals.get("url")
|
||||||
|
if url:
|
||||||
|
# Try to get repository by URL
|
||||||
|
repo_id = self._get_repo_id_by_url(
|
||||||
|
url=url, create=False, raise_if_invalid=False
|
||||||
|
)
|
||||||
|
if repo_id:
|
||||||
|
existing_repo_ids.append(repo_id)
|
||||||
|
continue
|
||||||
|
# Parse URL and update vals
|
||||||
|
parsed_url_dict = self._parse_url(url=url, raise_if_invalid=True)
|
||||||
|
vals.update(parsed_url_dict)
|
||||||
|
# Otherwise, add to create list
|
||||||
|
vals_list_to_create.append(vals)
|
||||||
|
# Compose the result
|
||||||
|
if vals_list_to_create:
|
||||||
|
res |= super().create(vals_list_to_create)
|
||||||
|
if existing_repo_ids:
|
||||||
|
res |= self.browse(existing_repo_ids)
|
||||||
|
self.clear_caches()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
"""Write repositories."""
|
||||||
|
res = super().write(vals)
|
||||||
|
self.clear_caches()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
"""Unlink repositories."""
|
||||||
|
res = super().unlink()
|
||||||
|
self.clear_caches()
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def name_create(self, name):
|
||||||
|
"""
|
||||||
|
Create a new repository from a URL.
|
||||||
|
"""
|
||||||
|
repo_id = self._get_repo_id_by_url(url=name, create=True, raise_if_invalid=True)
|
||||||
|
repo = self.browse(repo_id)
|
||||||
|
|
||||||
|
return repo_id, repo.display_name
|
||||||
|
|
||||||
|
@ormcache("self.env.uid", "self.env.su", "url")
|
||||||
|
def _get_repo_id_by_url(self, url, create=False, raise_if_invalid=False):
|
||||||
|
"""Get repository id by URL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (Char): URL to get repository id
|
||||||
|
create (Bool, optional): Create repository if not found.
|
||||||
|
Default is False.
|
||||||
|
raise_if_invalid (Bool, optional): Raise ValidationError
|
||||||
|
if the URL is not valid. Default is False.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Repository ID
|
||||||
|
or False if the URL is not valid and raise_if_invalid is False
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValidationError: If the URL is not valid and raise_if_invalid is True
|
||||||
|
"""
|
||||||
|
# Parse URL
|
||||||
|
parsed_url_dict = self._parse_url(url, raise_if_invalid=raise_if_invalid)
|
||||||
|
if not parsed_url_dict:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if repository already exists and use it
|
||||||
|
repo = self.search(
|
||||||
|
[
|
||||||
|
("repo", "=", parsed_url_dict["repo"]),
|
||||||
|
("host", "=", parsed_url_dict["host"]),
|
||||||
|
("owner_id", "=", parsed_url_dict["owner_id"]),
|
||||||
|
],
|
||||||
|
limit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Otherwise, create a new one
|
||||||
|
if not repo and create:
|
||||||
|
repo = self.create(parsed_url_dict)
|
||||||
|
|
||||||
|
return repo.id if repo else False
|
||||||
|
|
||||||
|
def _parse_url(self, url, raise_if_invalid=True):
|
||||||
|
"""Parse URL to get name, host and owner.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (Char): URL to parse
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValidationError: If the URL is not valid
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: Dictionary with name, host and owner
|
||||||
|
or empty dict if the URL is not valid and raise_if_invalid is False
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Validate URL
|
||||||
|
if not giturlparse.validate(url):
|
||||||
|
if raise_if_invalid:
|
||||||
|
raise ValidationError(_("Not a valid repository URL!"))
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Parse URL
|
||||||
|
parsed_url = giturlparse.parse(url)
|
||||||
|
|
||||||
|
# Get or create owner
|
||||||
|
owner_id = self.env["cx.tower.git.repo.owner"]._get_owner_id_by_name(
|
||||||
|
name=parsed_url.owner,
|
||||||
|
create=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get provider based on host
|
||||||
|
provider = self._get_provider(parsed_url)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"repo": parsed_url.repo,
|
||||||
|
"host": parsed_url.host,
|
||||||
|
"owner_id": owner_id,
|
||||||
|
"provider": provider,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_provider(self, parsed_url):
|
||||||
|
"""Get provider.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parsed_url (GitUrlParsed): Parsed URL object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Provider name
|
||||||
|
"""
|
||||||
|
provider = "other"
|
||||||
|
if parsed_url.assembla:
|
||||||
|
provider = "assembla"
|
||||||
|
elif parsed_url.bitbucket or "bitbucket" in parsed_url.host:
|
||||||
|
provider = "bitbucket"
|
||||||
|
elif parsed_url.gitlab:
|
||||||
|
provider = "gitlab"
|
||||||
|
elif parsed_url.github:
|
||||||
|
provider = "github"
|
||||||
|
|
||||||
|
return provider
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# YAML mixin methods
|
||||||
|
# ------------------------------
|
||||||
|
def _get_fields_for_yaml(self):
|
||||||
|
res = super()._get_fields_for_yaml()
|
||||||
|
res += [
|
||||||
|
"url",
|
||||||
|
"is_private",
|
||||||
|
"secret_id",
|
||||||
|
]
|
||||||
|
return res
|
||||||
107
addons/cetmix_tower_git/models/cx_tower_git_repo_owner.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
from odoo.tools import ormcache
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerGitRepoOwner(models.Model):
|
||||||
|
"""
|
||||||
|
Git Repository Owner.
|
||||||
|
Represents an organization or user that owns repositories.
|
||||||
|
Examples: "cetmix", "OCA", etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cx.tower.git.repo.owner"
|
||||||
|
_inherit = ["cx.tower.reference.mixin", "cx.tower.yaml.mixin"]
|
||||||
|
_description = "Cetmix Tower Git Repository Owner"
|
||||||
|
_order = "name"
|
||||||
|
|
||||||
|
display_name = fields.Char(
|
||||||
|
readonly=False, compute="_compute_display_name", store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
name = fields.Char(
|
||||||
|
help="Name of the repository owner (e.g., 'cetmix', 'OCA')",
|
||||||
|
)
|
||||||
|
reference = fields.Char(
|
||||||
|
index=True,
|
||||||
|
compute="_compute_display_name",
|
||||||
|
required=False,
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
|
repo_ids = fields.One2many(
|
||||||
|
comodel_name="cx.tower.git.repo",
|
||||||
|
inverse_name="owner_id",
|
||||||
|
string="Repositories",
|
||||||
|
copy=False,
|
||||||
|
help="Repositories owned by this organization/user",
|
||||||
|
)
|
||||||
|
secret_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.key",
|
||||||
|
string="Secret",
|
||||||
|
domain="[('key_type', '=', 's')]",
|
||||||
|
help="Custom secret used for this repository owner",
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("name")
|
||||||
|
def _compute_display_name(self):
|
||||||
|
"""Compute display name."""
|
||||||
|
for owner in self:
|
||||||
|
# By default, display name is the same as name
|
||||||
|
name = owner.name
|
||||||
|
owner.update(
|
||||||
|
{
|
||||||
|
"display_name": name or False,
|
||||||
|
"reference": owner._generate_or_fix_reference(name)
|
||||||
|
if name
|
||||||
|
else False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@ormcache("self.env.uid", "self.env.su", "name")
|
||||||
|
def _get_owner_id_by_name(self, name, create=False):
|
||||||
|
"""Get owner id by name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Owner name
|
||||||
|
create (bool): Create owner if not found
|
||||||
|
Returns:
|
||||||
|
int: Owner ID or None if not found
|
||||||
|
"""
|
||||||
|
owner = self.search([("name", "=ilike", name)], limit=1) if name else None
|
||||||
|
if not owner and create and name:
|
||||||
|
owner = self.create({"name": name})
|
||||||
|
return owner.id if owner else None
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
"""Clear cache on create."""
|
||||||
|
res = super().create(vals_list)
|
||||||
|
self.clear_caches()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
"""Clear cache on write."""
|
||||||
|
res = super().write(vals)
|
||||||
|
if "name" in vals:
|
||||||
|
self.clear_caches()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
"""Clear cache on unlink."""
|
||||||
|
res = super().unlink()
|
||||||
|
self.clear_caches()
|
||||||
|
return res
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# YAML mixin methods
|
||||||
|
# ------------------------------
|
||||||
|
def _get_fields_for_yaml(self):
|
||||||
|
res = super()._get_fields_for_yaml()
|
||||||
|
res += [
|
||||||
|
"display_name",
|
||||||
|
"name",
|
||||||
|
"secret_id",
|
||||||
|
]
|
||||||
|
return res
|
||||||
189
addons/cetmix_tower_git/models/cx_tower_git_source.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerGitSource(models.Model):
|
||||||
|
"""
|
||||||
|
Git Source.
|
||||||
|
Implements single git source.
|
||||||
|
Each source can include multiple remotes which can be
|
||||||
|
branches or pull requests of different repositories.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cx.tower.git.source"
|
||||||
|
_description = "Cetmix Tower Git Source"
|
||||||
|
|
||||||
|
_inherit = [
|
||||||
|
"cx.tower.reference.mixin",
|
||||||
|
"cx.tower.yaml.mixin",
|
||||||
|
]
|
||||||
|
_order = "sequence, name"
|
||||||
|
|
||||||
|
active = fields.Boolean(related="git_project_id.active", store=True, readonly=True)
|
||||||
|
enabled = fields.Boolean(
|
||||||
|
default=True, help="Enable in configuration and exported to files"
|
||||||
|
)
|
||||||
|
name = fields.Char(required=False)
|
||||||
|
sequence = fields.Integer(default=10)
|
||||||
|
git_project_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.git.project",
|
||||||
|
string="Git Configuration",
|
||||||
|
required=True,
|
||||||
|
ondelete="cascade",
|
||||||
|
auto_join=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
remote_ids = fields.One2many(
|
||||||
|
comodel_name="cx.tower.git.remote",
|
||||||
|
inverse_name="source_id",
|
||||||
|
auto_join=True,
|
||||||
|
copy=True,
|
||||||
|
)
|
||||||
|
remote_count = fields.Integer(
|
||||||
|
compute="_compute_remote_count",
|
||||||
|
string="Remotes",
|
||||||
|
)
|
||||||
|
remote_count_private = fields.Integer(
|
||||||
|
compute="_compute_remote_count",
|
||||||
|
string="Private Remotes",
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("remote_ids", "remote_ids.enabled", "remote_ids.is_private")
|
||||||
|
def _compute_remote_count(self):
|
||||||
|
for record in self:
|
||||||
|
remote_count = private_remote_count = 0
|
||||||
|
for remote in record.remote_ids:
|
||||||
|
if not remote.enabled:
|
||||||
|
continue
|
||||||
|
if remote.is_private:
|
||||||
|
private_remote_count += 1
|
||||||
|
remote_count += 1
|
||||||
|
record.update(
|
||||||
|
{
|
||||||
|
"remote_count": remote_count,
|
||||||
|
"remote_count_private": private_remote_count,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
res = super().create(vals_list)
|
||||||
|
# Update name
|
||||||
|
res._compose_name()
|
||||||
|
# Update related files and templates on create
|
||||||
|
res._update_related_files_and_templates()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
res = super().write(vals)
|
||||||
|
# Compose name
|
||||||
|
if "name" in vals and not vals.get("name"):
|
||||||
|
self._compose_name()
|
||||||
|
# Update related files and templates on update
|
||||||
|
self._update_related_files_and_templates()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
"""
|
||||||
|
Override to update related files and templates on unlink
|
||||||
|
"""
|
||||||
|
related_files = self.mapped("git_project_id").mapped("git_project_rel_ids")
|
||||||
|
related_templates = self.mapped("git_project_id").mapped(
|
||||||
|
"git_project_file_template_rel_ids"
|
||||||
|
)
|
||||||
|
res = super().unlink()
|
||||||
|
# Update related files and templates on unlink
|
||||||
|
if related_files:
|
||||||
|
related_files._save_to_file()
|
||||||
|
if related_templates:
|
||||||
|
related_templates._save_to_file_template()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _compose_name(self):
|
||||||
|
"""Compose name if not provided explicitly"""
|
||||||
|
for source in self:
|
||||||
|
if source.name:
|
||||||
|
continue
|
||||||
|
remote = fields.first(source.remote_ids)
|
||||||
|
if not remote:
|
||||||
|
source.name = _("Empty Source")
|
||||||
|
continue
|
||||||
|
|
||||||
|
remote_repo = remote.repo_id
|
||||||
|
source.name = f"{remote_repo.owner_id.name}/{remote_repo.repo}"
|
||||||
|
|
||||||
|
def _update_related_files_and_templates(self):
|
||||||
|
# Update related files and templates on update
|
||||||
|
related_files = self.mapped("git_project_id").mapped("git_project_rel_ids")
|
||||||
|
if related_files:
|
||||||
|
related_files._save_to_file()
|
||||||
|
related_templates = self.mapped("git_project_id").mapped(
|
||||||
|
"git_project_file_template_rel_ids"
|
||||||
|
)
|
||||||
|
if related_templates:
|
||||||
|
related_templates._save_to_file_template()
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Reference mixin methods
|
||||||
|
# ------------------------------
|
||||||
|
def _get_pre_populated_model_data(self):
|
||||||
|
res = super()._get_pre_populated_model_data()
|
||||||
|
res.update({"cx.tower.git.source": ["cx.tower.git.project", "git_project_id"]})
|
||||||
|
return res
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# YAML mixin methods
|
||||||
|
# ------------------------------
|
||||||
|
def _get_fields_for_yaml(self):
|
||||||
|
res = super()._get_fields_for_yaml()
|
||||||
|
res += [
|
||||||
|
"name",
|
||||||
|
"enabled",
|
||||||
|
"sequence",
|
||||||
|
"remote_ids",
|
||||||
|
]
|
||||||
|
return res
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Git Aggregator related methods
|
||||||
|
# ------------------------------
|
||||||
|
def _git_aggregator_prepare_record(self):
|
||||||
|
"""Prepare json structure for git aggregator.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: Json structure for git aggregator
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
|
||||||
|
# Prepare remotes, merges and target
|
||||||
|
remotes = {}
|
||||||
|
merges = []
|
||||||
|
target = None
|
||||||
|
for remote in self.remote_ids:
|
||||||
|
if remote.enabled:
|
||||||
|
remotes.update({remote.name: remote._git_aggregator_prepare_url()})
|
||||||
|
merges.append(
|
||||||
|
{
|
||||||
|
"remote": remote.name,
|
||||||
|
"ref": remote._git_aggregator_prepare_head(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Set target to first remote name
|
||||||
|
if not target:
|
||||||
|
target = remote.name
|
||||||
|
|
||||||
|
# If no remotes, return empty dict
|
||||||
|
if not remotes:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
vals = {
|
||||||
|
"remotes": remotes,
|
||||||
|
"merges": merges,
|
||||||
|
"target": target,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fetch only first commit if there is only one remote
|
||||||
|
if len(remotes) == 1:
|
||||||
|
vals.update({"defaults": {"depth": 1}})
|
||||||
|
return vals
|
||||||
32
addons/cetmix_tower_git/models/cx_tower_plan_line.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Copyright (C) 2025 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerPlanLine(models.Model):
|
||||||
|
"""Flight Plan Line"""
|
||||||
|
|
||||||
|
_inherit = "cx.tower.plan.line"
|
||||||
|
|
||||||
|
git_project_id = fields.Many2one(
|
||||||
|
comodel_name="cx.tower.git.project",
|
||||||
|
string="Git Project",
|
||||||
|
help="Select a git project to be linked to the file and server.",
|
||||||
|
)
|
||||||
|
is_make_copy = fields.Boolean(
|
||||||
|
string="Make a Copy",
|
||||||
|
help="Create a copy of the Git Project instead of linking "
|
||||||
|
"the file to the existing one.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# YAML mixin methods
|
||||||
|
# ------------------------------
|
||||||
|
def _get_fields_for_yaml(self):
|
||||||
|
res = super()._get_fields_for_yaml()
|
||||||
|
res += [
|
||||||
|
"git_project_id",
|
||||||
|
"is_make_copy",
|
||||||
|
]
|
||||||
|
return res
|
||||||
156
addons/cetmix_tower_git/models/cx_tower_server.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerServer(models.Model):
|
||||||
|
_inherit = "cx.tower.server"
|
||||||
|
|
||||||
|
git_project_rel_ids = fields.One2many(
|
||||||
|
comodel_name="cx.tower.git.project.rel",
|
||||||
|
inverse_name="server_id",
|
||||||
|
copy=False,
|
||||||
|
depends=["git_project_ids"],
|
||||||
|
groups="cetmix_tower_server.group_manager,cetmix_tower_server.group_root",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Helper field to get all git projects related to server
|
||||||
|
# IMPORTANT: This field may contain duplicates because of the relation nature!
|
||||||
|
git_project_ids = fields.Many2many(
|
||||||
|
comodel_name="cx.tower.git.project",
|
||||||
|
relation="cx_tower_git_project_rel",
|
||||||
|
column1="server_id",
|
||||||
|
column2="git_project_id",
|
||||||
|
readonly=True,
|
||||||
|
copy=False,
|
||||||
|
depends=["git_project_rel_ids"],
|
||||||
|
groups="cetmix_tower_server.group_manager,cetmix_tower_server.group_root",
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# YAML mixin methods
|
||||||
|
# ------------------------------
|
||||||
|
def _get_fields_for_yaml(self):
|
||||||
|
res = super()._get_fields_for_yaml()
|
||||||
|
res += [
|
||||||
|
"git_project_rel_ids",
|
||||||
|
]
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _get_force_x2m_resolve_models(self):
|
||||||
|
res = super()._get_force_x2m_resolve_models()
|
||||||
|
|
||||||
|
# Add File in order to always try to use existing one
|
||||||
|
res += ["cx.tower.file"]
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _update_or_create_related_record(
|
||||||
|
self, model, reference, values, create_immediately=False
|
||||||
|
):
|
||||||
|
# Files must be created immediately because they are related
|
||||||
|
# to both server and git project.
|
||||||
|
# So if a file is not created immediately when it is created
|
||||||
|
# for the server, the same file will be created for the git project.
|
||||||
|
# This will lead to creation of two files with the same content
|
||||||
|
# for the same server.
|
||||||
|
|
||||||
|
if model._name == "cx.tower.file":
|
||||||
|
create_immediately = True
|
||||||
|
return super()._update_or_create_related_record(
|
||||||
|
model, reference, values, create_immediately=create_immediately
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_servers_by_git_ref(self, repository_url, head=None, head_type=None):
|
||||||
|
"""
|
||||||
|
Return servers linked to a given Git repository reference.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
repository_url : str
|
||||||
|
Pre-normalized canonical Git URL
|
||||||
|
(e.g. ``https://host/owner/repo.git``).
|
||||||
|
head : str, optional
|
||||||
|
Branch name, commit SHA, or PR identifier.
|
||||||
|
head_type : {'branch', 'commit', 'pr'}, optional
|
||||||
|
Type of the ``head`` argument.
|
||||||
|
If only ``head`` is provided, it will match across all head types.
|
||||||
|
If only ``head_type`` is provided, it will filter by type regardless of head
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
recordset of cx.tower.server
|
||||||
|
Matching servers. Empty recordset if no matches.
|
||||||
|
"""
|
||||||
|
|
||||||
|
server_obj = self.env["cx.tower.server"]
|
||||||
|
# URL MUST be already canonical.
|
||||||
|
if not repository_url:
|
||||||
|
return server_obj
|
||||||
|
|
||||||
|
# Get repository id by URL
|
||||||
|
repo_id = self.env["cx.tower.git.repo"]._get_repo_id_by_url(
|
||||||
|
repository_url, raise_if_invalid=False
|
||||||
|
)
|
||||||
|
if not repo_id:
|
||||||
|
return server_obj
|
||||||
|
repo = self.env["cx.tower.git.repo"].browse(repo_id)
|
||||||
|
|
||||||
|
# Compose domain for remotes
|
||||||
|
remote_domain = [
|
||||||
|
("source_id.enabled", "=", True),
|
||||||
|
("enabled", "=", True),
|
||||||
|
]
|
||||||
|
if head:
|
||||||
|
head = self.env["cx.tower.git.remote"]._sanitize_head(head)
|
||||||
|
remote_domain.append(("head", "=", head))
|
||||||
|
if head_type:
|
||||||
|
remote_domain.append(("head_type", "=", head_type))
|
||||||
|
|
||||||
|
# Get remotes
|
||||||
|
remotes = repo.remote_ids.filtered_domain(remote_domain)
|
||||||
|
if not remotes:
|
||||||
|
return server_obj
|
||||||
|
|
||||||
|
# Get servers from remotes
|
||||||
|
servers = remotes.mapped("git_project_id.git_project_rel_ids.server_id")
|
||||||
|
return servers
|
||||||
|
|
||||||
|
def _command_runner_file_using_template_create_file(
|
||||||
|
self,
|
||||||
|
file_template_id,
|
||||||
|
server_dir,
|
||||||
|
plan_line,
|
||||||
|
if_file_exists,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""Override to create git project relation
|
||||||
|
when creating a file using a template.
|
||||||
|
"""
|
||||||
|
file = super()._command_runner_file_using_template_create_file(
|
||||||
|
file_template_id, server_dir, plan_line, if_file_exists, **kwargs
|
||||||
|
)
|
||||||
|
if file and plan_line:
|
||||||
|
git_project = plan_line.git_project_id
|
||||||
|
if not git_project:
|
||||||
|
return file
|
||||||
|
|
||||||
|
if plan_line.is_make_copy:
|
||||||
|
# Remove default_server_ids from context, because this relation
|
||||||
|
# will be created through git_project_rel_ids.
|
||||||
|
# default_server_ids will interfere at the moment when
|
||||||
|
# pairs of values are created through SQL query
|
||||||
|
# in the method write_real and it does not take into account
|
||||||
|
# that in this case we are creating a copy of the git project
|
||||||
|
git_project = git_project.with_context(default_server_ids=False).copy()
|
||||||
|
|
||||||
|
self.env["cx.tower.git.project.rel"].create(
|
||||||
|
{
|
||||||
|
"git_project_id": git_project.id,
|
||||||
|
"server_id": self.id,
|
||||||
|
"file_id": file.id,
|
||||||
|
"project_format": git_project._default_project_format(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return file
|
||||||
3
addons/cetmix_tower_git/pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["whool"]
|
||||||
|
build-backend = "whool.buildapi"
|
||||||
1
addons/cetmix_tower_git/readme/CONFIGURE.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Please refer to the [official documentation](https://cetmix.com/tower) for detailed configuration instructions.
|
||||||
3
addons/cetmix_tower_git/readme/DESCRIPTION.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
This module implements Git Management functionality for [Cetmix Tower](https://cetmix.com/tower).
|
||||||
|
|
||||||
|
Please refer to the [official documentation](https://cetmix.com/tower) for detailed information.
|
||||||
43
addons/cetmix_tower_git/readme/HISTORY.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
## 16.0.2.0.1 (2025-12-11)
|
||||||
|
|
||||||
|
- Features: Improve search views, implement the search panel for selected views. (5139)
|
||||||
|
|
||||||
|
|
||||||
|
## 16.0.2.0.0 (2025-10-27)
|
||||||
|
|
||||||
|
- Features: Major refactoring: implement Git repository entity. (4914)
|
||||||
|
|
||||||
|
|
||||||
|
## 16.0.1.0.6 (2025-08-18)
|
||||||
|
|
||||||
|
- Features: Link or copy a git project when uploading the linked file using command (4759)
|
||||||
|
|
||||||
|
|
||||||
|
## 16.0.1.0.5 (2025-08-17)
|
||||||
|
|
||||||
|
- Features: Search servers by git reference (4838)
|
||||||
|
|
||||||
|
|
||||||
|
## 16.0.1.0.4 (2025-07-29)
|
||||||
|
|
||||||
|
- Features: Export related commands and flight plans together with server (4849)
|
||||||
|
|
||||||
|
|
||||||
|
## 16.0.1.0.3 (2025-05-23)
|
||||||
|
|
||||||
|
- Bugfixes: Duplicated file is created when importing a YAML file with a git project. (4715)
|
||||||
|
|
||||||
|
|
||||||
|
## 16.0.1.0.2 (2025-05-16)
|
||||||
|
|
||||||
|
- Features: Record references for git relations. (4670)
|
||||||
|
|
||||||
|
|
||||||
|
## 16.0.1.0.1 (2025-05-09)
|
||||||
|
|
||||||
|
- Bugfixes: Non-critical issues and performance improvements. (4663)
|
||||||
|
|
||||||
|
|
||||||
|
## 16.0.1.0.0
|
||||||
|
|
||||||
|
Release for Odoo 16.0
|
||||||
1
addons/cetmix_tower_git/readme/USAGE.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Please refer to the [official documentation](https://cetmix.com/tower) for detailed usage instructions.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Manager Read Rule -->
|
||||||
|
<record id="rule_git_project_file_template_rel_manager_read" model="ir.rule">
|
||||||
|
<field
|
||||||
|
name="name"
|
||||||
|
>Git Project File Template Relation: Manager Read Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project_file_template_rel" />
|
||||||
|
<field name="domain_force">['&',
|
||||||
|
'|',
|
||||||
|
('git_project_id.user_ids', 'in', [user.id]),
|
||||||
|
('git_project_id.manager_ids', 'in', [user.id]),
|
||||||
|
'|',
|
||||||
|
('file_template_id.user_ids', 'in', [user.id]),
|
||||||
|
('file_template_id.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="1" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Manager Write/Create/Delete Rule -->
|
||||||
|
<record id="rule_git_project_file_template_rel_manager_write" model="ir.rule">
|
||||||
|
<field
|
||||||
|
name="name"
|
||||||
|
>Git Project File Template Relation: Manager Write Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project_file_template_rel" />
|
||||||
|
<field name="domain_force">[
|
||||||
|
('git_project_id.manager_ids', 'in', [user.id]),
|
||||||
|
('file_template_id.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="0" />
|
||||||
|
<field name="perm_write" eval="1" />
|
||||||
|
<field name="perm_create" eval="1" />
|
||||||
|
<field name="perm_unlink" eval="1" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Root Access Rule -->
|
||||||
|
<record id="rule_git_project_file_template_rel_root" model="ir.rule">
|
||||||
|
<field name="name">Git Project File Template Relation: Root Full Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project_file_template_rel" />
|
||||||
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_root'))]" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Manager Read Rule -->
|
||||||
|
<record id="rule_git_project_rel_manager_read" model="ir.rule">
|
||||||
|
<field name="name">Git Project Relation: Manager Read Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project_rel" />
|
||||||
|
<field name="domain_force">['&',
|
||||||
|
'|',
|
||||||
|
('git_project_id.user_ids', 'in', [user.id]),
|
||||||
|
('git_project_id.manager_ids', 'in', [user.id]),
|
||||||
|
'|',
|
||||||
|
('server_id.user_ids', 'in', [user.id]),
|
||||||
|
('server_id.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="1" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Manager Write/Create/Delete Rule -->
|
||||||
|
<record id="rule_git_project_rel_manager_create_write_unlink" model="ir.rule">
|
||||||
|
<field
|
||||||
|
name="name"
|
||||||
|
>Git Project Relation: Manager Create/Write/Delete Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project_rel" />
|
||||||
|
<field name="domain_force">[('git_project_id.manager_ids', 'in', [user.id]),
|
||||||
|
('server_id.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="0" />
|
||||||
|
<field name="perm_write" eval="1" />
|
||||||
|
<field name="perm_create" eval="1" />
|
||||||
|
<field name="perm_unlink" eval="1" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Root Access Rule -->
|
||||||
|
<record id="rule_git_project_rel_root" model="ir.rule">
|
||||||
|
<field name="name">Git Project Relation: Root Full Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project_rel" />
|
||||||
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_root'))]" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Manager Read Rule -->
|
||||||
|
<record id="rule_git_project_manager_read" model="ir.rule">
|
||||||
|
<field name="name">Git Project: Manager Read Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project" />
|
||||||
|
<field
|
||||||
|
name="domain_force"
|
||||||
|
>['|', ('user_ids', 'in', [user.id]), ('manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="1" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Additional Manager Read Rule with Server Access -->
|
||||||
|
<record id="rule_git_project_manager_read_server" model="ir.rule">
|
||||||
|
<field name="name">Git Project: Manager Read Access via Server</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project" />
|
||||||
|
<field name="domain_force">['|',
|
||||||
|
('server_ids.user_ids', 'in', [user.id]),
|
||||||
|
('server_ids.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="1" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Manager Write/Create Rule -->
|
||||||
|
<record id="rule_git_project_manager_write" model="ir.rule">
|
||||||
|
<field name="name">Git Project: Manager Write Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project" />
|
||||||
|
<field name="domain_force">[('manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="0" />
|
||||||
|
<field name="perm_write" eval="1" />
|
||||||
|
<field name="perm_create" eval="1" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Manager Delete Rule -->
|
||||||
|
<record id="rule_git_project_manager_unlink" model="ir.rule">
|
||||||
|
<field name="name">Git Project: Manager Delete Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project" />
|
||||||
|
<field
|
||||||
|
name="domain_force"
|
||||||
|
>[('manager_ids', 'in', [user.id]), ('create_uid', '=', user.id)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="0" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="1" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Root Access Rule -->
|
||||||
|
<record id="rule_git_project_root" model="ir.rule">
|
||||||
|
<field name="name">Git Project: Root Full Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_project" />
|
||||||
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_root'))]" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Manager Read Rule -->
|
||||||
|
<record id="rule_git_remote_manager_read" model="ir.rule">
|
||||||
|
<field name="name">Git Remote: Manager Read Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_remote" />
|
||||||
|
<field
|
||||||
|
name="domain_force"
|
||||||
|
>['|', ('git_project_id.user_ids', 'in', [user.id]), ('git_project_id.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="1" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Additional Manager Read Rule with Server Access -->
|
||||||
|
<record id="rule_git_remote_manager_read_server" model="ir.rule">
|
||||||
|
<field name="name">Git Remote: Manager Read Access via Server</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_remote" />
|
||||||
|
<field name="domain_force">['|',
|
||||||
|
('git_project_id.server_ids.user_ids', 'in', [user.id]),
|
||||||
|
('git_project_id.server_ids.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="1" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Manager Write/Create Rule -->
|
||||||
|
<record id="rule_git_remote_manager_write" model="ir.rule">
|
||||||
|
<field name="name">Git Remote: Manager Write/Create Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_remote" />
|
||||||
|
<field
|
||||||
|
name="domain_force"
|
||||||
|
>[('git_project_id.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="0" />
|
||||||
|
<field name="perm_write" eval="1" />
|
||||||
|
<field name="perm_create" eval="1" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Manager Delete Rule -->
|
||||||
|
<record id="rule_git_remote_manager_unlink" model="ir.rule">
|
||||||
|
<field name="name">Git Remote: Manager Delete Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_remote" />
|
||||||
|
<field
|
||||||
|
name="domain_force"
|
||||||
|
>[('git_project_id.manager_ids', 'in', [user.id]), ('create_uid', '=', user.id)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="0" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="1" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Root Access Rule -->
|
||||||
|
<record id="rule_git_remote_root" model="ir.rule">
|
||||||
|
<field name="name">Git Remote: Root Full Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_remote" />
|
||||||
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_root'))]" />
|
||||||
|
<field name="perm_read" eval="1" />
|
||||||
|
<field name="perm_write" eval="1" />
|
||||||
|
<field name="perm_create" eval="1" />
|
||||||
|
<field name="perm_unlink" eval="1" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Manager Read Rule - View All Records - nothing to add as this is default -->
|
||||||
|
|
||||||
|
<!-- Manager Write/Create Rule -->
|
||||||
|
<record id="rule_git_repo_owner_manager_write" model="ir.rule">
|
||||||
|
<field name="name">Git Repository Owner: Manager Write/Create Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_repo_owner" />
|
||||||
|
<field name="domain_force">[('create_uid', '=', user.id)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="0" />
|
||||||
|
<field name="perm_write" eval="1" />
|
||||||
|
<field name="perm_create" eval="1" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Root Access Rule -->
|
||||||
|
<record id="rule_git_repo_owner_root" model="ir.rule">
|
||||||
|
<field name="name">Git Repository Owner: Root Full Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_repo_owner" />
|
||||||
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_root'))]" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Manager Read Rule - View All Records - nothing to add as this is default -->
|
||||||
|
|
||||||
|
<!-- Manager Write/Create Rule -->
|
||||||
|
<record id="rule_git_repo_manager_write" model="ir.rule">
|
||||||
|
<field name="name">Git Repository: Manager Write/Create Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_repo" />
|
||||||
|
<field name="domain_force">[('create_uid', '=', user.id)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="0" />
|
||||||
|
<field name="perm_write" eval="1" />
|
||||||
|
<field name="perm_create" eval="1" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Root Access Rule -->
|
||||||
|
<record id="rule_git_repo_root" model="ir.rule">
|
||||||
|
<field name="name">Git Repository: Root Full Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_repo" />
|
||||||
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_root'))]" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Git Source Record Rules -->
|
||||||
|
|
||||||
|
<!-- Manager Read Rule -->
|
||||||
|
<record id="rule_git_source_manager_read" model="ir.rule">
|
||||||
|
<field name="name">Git Source: Manager Read Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_source" />
|
||||||
|
<field
|
||||||
|
name="domain_force"
|
||||||
|
>['|', ('git_project_id.user_ids', 'in', [user.id]), ('git_project_id.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="1" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Additional Manager Read Rule with Server Access -->
|
||||||
|
<record id="rule_git_source_manager_read_server" model="ir.rule">
|
||||||
|
<field name="name">Git Source: Manager Read Access via Server</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_source" />
|
||||||
|
<field name="domain_force">['|',
|
||||||
|
('git_project_id.server_ids.user_ids', 'in', [user.id]),
|
||||||
|
('git_project_id.server_ids.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="1" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Manager Write/Create Rule -->
|
||||||
|
<record id="rule_git_source_manager_write" model="ir.rule">
|
||||||
|
<field name="name">Git Source: Manager Write/Create Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_source" />
|
||||||
|
<field
|
||||||
|
name="domain_force"
|
||||||
|
>[('git_project_id.manager_ids', 'in', [user.id])]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="0" />
|
||||||
|
<field name="perm_write" eval="1" />
|
||||||
|
<field name="perm_create" eval="1" />
|
||||||
|
<field name="perm_unlink" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Manager Delete Rule -->
|
||||||
|
<record id="rule_git_source_manager_unlink" model="ir.rule">
|
||||||
|
<field name="name">Git Source: Manager Delete Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_source" />
|
||||||
|
<field
|
||||||
|
name="domain_force"
|
||||||
|
>[('git_project_id.manager_ids', 'in', [user.id]), ('create_uid', '=', user.id)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_manager'))]" />
|
||||||
|
<field name="perm_read" eval="0" />
|
||||||
|
<field name="perm_write" eval="0" />
|
||||||
|
<field name="perm_create" eval="0" />
|
||||||
|
<field name="perm_unlink" eval="1" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Root Access Rule -->
|
||||||
|
<record id="rule_git_source_root" model="ir.rule">
|
||||||
|
<field name="name">Git Source: Root Full Access</field>
|
||||||
|
<field name="model_id" ref="model_cx_tower_git_source" />
|
||||||
|
<field name="domain_force">[(1, '=', 1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('cetmix_tower_server.group_root'))]" />
|
||||||
|
<field name="perm_read" eval="1" />
|
||||||
|
<field name="perm_write" eval="1" />
|
||||||
|
<field name="perm_create" eval="1" />
|
||||||
|
<field name="perm_unlink" eval="1" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
15
addons/cetmix_tower_git/security/ir.model.access.csv
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_git_config_manager,Git Config Manager,model_cx_tower_git_project,cetmix_tower_server.group_manager,1,1,1,1
|
||||||
|
access_git_config_root,Git Config Root,model_cx_tower_git_project,cetmix_tower_server.group_root,1,1,1,1
|
||||||
|
access_git_source_manager,Git Source Manager,model_cx_tower_git_source,cetmix_tower_server.group_manager,1,1,1,1
|
||||||
|
access_git_source_root,Git Source Root,model_cx_tower_git_source,cetmix_tower_server.group_root,1,1,1,1
|
||||||
|
access_git_remote_manager,Git Remote Manager,model_cx_tower_git_remote,cetmix_tower_server.group_manager,1,1,1,1
|
||||||
|
access_git_remote_root,Git Remote Root,model_cx_tower_git_remote,cetmix_tower_server.group_root,1,1,1,1
|
||||||
|
access_git_repo_manager,Git Repository Manager,model_cx_tower_git_repo,cetmix_tower_server.group_manager,1,1,1,1
|
||||||
|
access_git_repo_root,Git Repository Root,model_cx_tower_git_repo,cetmix_tower_server.group_root,1,1,1,1
|
||||||
|
access_git_repo_owner_manager,Git Repository Owner Manager,model_cx_tower_git_repo_owner,cetmix_tower_server.group_manager,1,1,1,0
|
||||||
|
access_git_repo_owner_root,Git Repository Owner Root,model_cx_tower_git_repo_owner,cetmix_tower_server.group_root,1,1,1,1
|
||||||
|
access_git_project_server_file_rel,Git Project Server File Rel Manager,model_cx_tower_git_project_rel,cetmix_tower_server.group_manager,1,1,1,1
|
||||||
|
access_git_project_server_file_rel_root,Git Project Server File Rel Root,model_cx_tower_git_project_rel,cetmix_tower_server.group_root,1,1,1,1
|
||||||
|
access_git_project_file_template_rel,Git Project File Template Rel Manager,model_cx_tower_git_project_file_template_rel,cetmix_tower_server.group_manager,1,1,1,1
|
||||||
|
access_git_project_file_template_rel_root,Git Project File Template Rel Root,model_cx_tower_git_project_file_template_rel,cetmix_tower_server.group_root,1,1,1,1
|
||||||
|
BIN
addons/cetmix_tower_git/static/description/banner.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
addons/cetmix_tower_git/static/description/icon.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
497
addons/cetmix_tower_git/static/description/index.html
Normal file
@@ -0,0 +1,497 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
|
||||||
|
<title>Cetmix Tower Git</title>
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
/*
|
||||||
|
:Author: David Goodger (goodger@python.org)
|
||||||
|
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
|
||||||
|
:Copyright: This stylesheet has been placed in the public domain.
|
||||||
|
|
||||||
|
Default cascading style sheet for the HTML output of Docutils.
|
||||||
|
Despite the name, some widely supported CSS2 features are used.
|
||||||
|
|
||||||
|
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
|
||||||
|
customize this style sheet.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* used to remove borders from tables and images */
|
||||||
|
.borderless, table.borderless td, table.borderless th {
|
||||||
|
border: 0 }
|
||||||
|
|
||||||
|
table.borderless td, table.borderless th {
|
||||||
|
/* Override padding for "table.docutils td" with "! important".
|
||||||
|
The right padding separates the table cells. */
|
||||||
|
padding: 0 0.5em 0 0 ! important }
|
||||||
|
|
||||||
|
.first {
|
||||||
|
/* Override more specific margin styles with "! important". */
|
||||||
|
margin-top: 0 ! important }
|
||||||
|
|
||||||
|
.last, .with-subtitle {
|
||||||
|
margin-bottom: 0 ! important }
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none }
|
||||||
|
|
||||||
|
.subscript {
|
||||||
|
vertical-align: sub;
|
||||||
|
font-size: smaller }
|
||||||
|
|
||||||
|
.superscript {
|
||||||
|
vertical-align: super;
|
||||||
|
font-size: smaller }
|
||||||
|
|
||||||
|
a.toc-backref {
|
||||||
|
text-decoration: none ;
|
||||||
|
color: black }
|
||||||
|
|
||||||
|
blockquote.epigraph {
|
||||||
|
margin: 2em 5em ; }
|
||||||
|
|
||||||
|
dl.docutils dd {
|
||||||
|
margin-bottom: 0.5em }
|
||||||
|
|
||||||
|
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||||
|
dl.docutils dt {
|
||||||
|
font-weight: bold }
|
||||||
|
*/
|
||||||
|
|
||||||
|
div.abstract {
|
||||||
|
margin: 2em 5em }
|
||||||
|
|
||||||
|
div.abstract p.topic-title {
|
||||||
|
font-weight: bold ;
|
||||||
|
text-align: center }
|
||||||
|
|
||||||
|
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||||
|
div.hint, div.important, div.note, div.tip, div.warning {
|
||||||
|
margin: 2em ;
|
||||||
|
border: medium outset ;
|
||||||
|
padding: 1em }
|
||||||
|
|
||||||
|
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||||
|
div.important p.admonition-title, div.note p.admonition-title,
|
||||||
|
div.tip p.admonition-title {
|
||||||
|
font-weight: bold ;
|
||||||
|
font-family: sans-serif }
|
||||||
|
|
||||||
|
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||||
|
div.danger p.admonition-title, div.error p.admonition-title,
|
||||||
|
div.warning p.admonition-title, .code .error {
|
||||||
|
color: red ;
|
||||||
|
font-weight: bold ;
|
||||||
|
font-family: sans-serif }
|
||||||
|
|
||||||
|
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||||
|
compound paragraphs.
|
||||||
|
div.compound .compound-first, div.compound .compound-middle {
|
||||||
|
margin-bottom: 0.5em }
|
||||||
|
|
||||||
|
div.compound .compound-last, div.compound .compound-middle {
|
||||||
|
margin-top: 0.5em }
|
||||||
|
*/
|
||||||
|
|
||||||
|
div.dedication {
|
||||||
|
margin: 2em 5em ;
|
||||||
|
text-align: center ;
|
||||||
|
font-style: italic }
|
||||||
|
|
||||||
|
div.dedication p.topic-title {
|
||||||
|
font-weight: bold ;
|
||||||
|
font-style: normal }
|
||||||
|
|
||||||
|
div.figure {
|
||||||
|
margin-left: 2em ;
|
||||||
|
margin-right: 2em }
|
||||||
|
|
||||||
|
div.footer, div.header {
|
||||||
|
clear: both;
|
||||||
|
font-size: smaller }
|
||||||
|
|
||||||
|
div.line-block {
|
||||||
|
display: block ;
|
||||||
|
margin-top: 1em ;
|
||||||
|
margin-bottom: 1em }
|
||||||
|
|
||||||
|
div.line-block div.line-block {
|
||||||
|
margin-top: 0 ;
|
||||||
|
margin-bottom: 0 ;
|
||||||
|
margin-left: 1.5em }
|
||||||
|
|
||||||
|
div.sidebar {
|
||||||
|
margin: 0 0 0.5em 1em ;
|
||||||
|
border: medium outset ;
|
||||||
|
padding: 1em ;
|
||||||
|
background-color: #ffffee ;
|
||||||
|
width: 40% ;
|
||||||
|
float: right ;
|
||||||
|
clear: right }
|
||||||
|
|
||||||
|
div.sidebar p.rubric {
|
||||||
|
font-family: sans-serif ;
|
||||||
|
font-size: medium }
|
||||||
|
|
||||||
|
div.system-messages {
|
||||||
|
margin: 5em }
|
||||||
|
|
||||||
|
div.system-messages h1 {
|
||||||
|
color: red }
|
||||||
|
|
||||||
|
div.system-message {
|
||||||
|
border: medium outset ;
|
||||||
|
padding: 1em }
|
||||||
|
|
||||||
|
div.system-message p.system-message-title {
|
||||||
|
color: red ;
|
||||||
|
font-weight: bold }
|
||||||
|
|
||||||
|
div.topic {
|
||||||
|
margin: 2em }
|
||||||
|
|
||||||
|
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||||
|
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||||
|
margin-top: 0.4em }
|
||||||
|
|
||||||
|
h1.title {
|
||||||
|
text-align: center }
|
||||||
|
|
||||||
|
h2.subtitle {
|
||||||
|
text-align: center }
|
||||||
|
|
||||||
|
hr.docutils {
|
||||||
|
width: 75% }
|
||||||
|
|
||||||
|
img.align-left, .figure.align-left, object.align-left, table.align-left {
|
||||||
|
clear: left ;
|
||||||
|
float: left ;
|
||||||
|
margin-right: 1em }
|
||||||
|
|
||||||
|
img.align-right, .figure.align-right, object.align-right, table.align-right {
|
||||||
|
clear: right ;
|
||||||
|
float: right ;
|
||||||
|
margin-left: 1em }
|
||||||
|
|
||||||
|
img.align-center, .figure.align-center, object.align-center {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.align-center {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-left {
|
||||||
|
text-align: left }
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
clear: both ;
|
||||||
|
text-align: center }
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
text-align: right }
|
||||||
|
|
||||||
|
/* reset inner alignment in figures */
|
||||||
|
div.align-right {
|
||||||
|
text-align: inherit }
|
||||||
|
|
||||||
|
/* div.align-center * { */
|
||||||
|
/* text-align: left } */
|
||||||
|
|
||||||
|
.align-top {
|
||||||
|
vertical-align: top }
|
||||||
|
|
||||||
|
.align-middle {
|
||||||
|
vertical-align: middle }
|
||||||
|
|
||||||
|
.align-bottom {
|
||||||
|
vertical-align: bottom }
|
||||||
|
|
||||||
|
ol.simple, ul.simple {
|
||||||
|
margin-bottom: 1em }
|
||||||
|
|
||||||
|
ol.arabic {
|
||||||
|
list-style: decimal }
|
||||||
|
|
||||||
|
ol.loweralpha {
|
||||||
|
list-style: lower-alpha }
|
||||||
|
|
||||||
|
ol.upperalpha {
|
||||||
|
list-style: upper-alpha }
|
||||||
|
|
||||||
|
ol.lowerroman {
|
||||||
|
list-style: lower-roman }
|
||||||
|
|
||||||
|
ol.upperroman {
|
||||||
|
list-style: upper-roman }
|
||||||
|
|
||||||
|
p.attribution {
|
||||||
|
text-align: right ;
|
||||||
|
margin-left: 50% }
|
||||||
|
|
||||||
|
p.caption {
|
||||||
|
font-style: italic }
|
||||||
|
|
||||||
|
p.credits {
|
||||||
|
font-style: italic ;
|
||||||
|
font-size: smaller }
|
||||||
|
|
||||||
|
p.label {
|
||||||
|
white-space: nowrap }
|
||||||
|
|
||||||
|
p.rubric {
|
||||||
|
font-weight: bold ;
|
||||||
|
font-size: larger ;
|
||||||
|
color: maroon ;
|
||||||
|
text-align: center }
|
||||||
|
|
||||||
|
p.sidebar-title {
|
||||||
|
font-family: sans-serif ;
|
||||||
|
font-weight: bold ;
|
||||||
|
font-size: larger }
|
||||||
|
|
||||||
|
p.sidebar-subtitle {
|
||||||
|
font-family: sans-serif ;
|
||||||
|
font-weight: bold }
|
||||||
|
|
||||||
|
p.topic-title {
|
||||||
|
font-weight: bold }
|
||||||
|
|
||||||
|
pre.address {
|
||||||
|
margin-bottom: 0 ;
|
||||||
|
margin-top: 0 ;
|
||||||
|
font: inherit }
|
||||||
|
|
||||||
|
pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
||||||
|
margin-left: 2em ;
|
||||||
|
margin-right: 2em }
|
||||||
|
|
||||||
|
pre.code .ln { color: gray; } /* line numbers */
|
||||||
|
pre.code, code { background-color: #eeeeee }
|
||||||
|
pre.code .comment, code .comment { color: #5C6576 }
|
||||||
|
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
|
||||||
|
pre.code .literal.string, code .literal.string { color: #0C5404 }
|
||||||
|
pre.code .name.builtin, code .name.builtin { color: #352B84 }
|
||||||
|
pre.code .deleted, code .deleted { background-color: #DEB0A1}
|
||||||
|
pre.code .inserted, code .inserted { background-color: #A3D289}
|
||||||
|
|
||||||
|
span.classifier {
|
||||||
|
font-family: sans-serif ;
|
||||||
|
font-style: oblique }
|
||||||
|
|
||||||
|
span.classifier-delimiter {
|
||||||
|
font-family: sans-serif ;
|
||||||
|
font-weight: bold }
|
||||||
|
|
||||||
|
span.interpreted {
|
||||||
|
font-family: sans-serif }
|
||||||
|
|
||||||
|
span.option {
|
||||||
|
white-space: nowrap }
|
||||||
|
|
||||||
|
span.pre {
|
||||||
|
white-space: pre }
|
||||||
|
|
||||||
|
span.problematic, pre.problematic {
|
||||||
|
color: red }
|
||||||
|
|
||||||
|
span.section-subtitle {
|
||||||
|
/* font-size relative to parent (h1..h6 element) */
|
||||||
|
font-size: 80% }
|
||||||
|
|
||||||
|
table.citation {
|
||||||
|
border-left: solid 1px gray;
|
||||||
|
margin-left: 1px }
|
||||||
|
|
||||||
|
table.docinfo {
|
||||||
|
margin: 2em 4em }
|
||||||
|
|
||||||
|
table.docutils {
|
||||||
|
margin-top: 0.5em ;
|
||||||
|
margin-bottom: 0.5em }
|
||||||
|
|
||||||
|
table.footnote {
|
||||||
|
border-left: solid 1px black;
|
||||||
|
margin-left: 1px }
|
||||||
|
|
||||||
|
table.docutils td, table.docutils th,
|
||||||
|
table.docinfo td, table.docinfo th {
|
||||||
|
padding-left: 0.5em ;
|
||||||
|
padding-right: 0.5em ;
|
||||||
|
vertical-align: top }
|
||||||
|
|
||||||
|
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||||
|
font-weight: bold ;
|
||||||
|
text-align: left ;
|
||||||
|
white-space: nowrap ;
|
||||||
|
padding-left: 0 }
|
||||||
|
|
||||||
|
/* "booktabs" style (no vertical lines) */
|
||||||
|
table.docutils.booktabs {
|
||||||
|
border: 0px;
|
||||||
|
border-top: 2px solid;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
table.docutils.booktabs * {
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
table.docutils.booktabs th {
|
||||||
|
border-bottom: thin solid;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||||
|
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||||
|
font-size: 100% }
|
||||||
|
|
||||||
|
ul.auto-toc {
|
||||||
|
list-style-type: none }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="document" id="cetmix-tower-git">
|
||||||
|
<h1 class="title">Cetmix Tower Git</h1>
|
||||||
|
|
||||||
|
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
|
!! changes will be overwritten. !!
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! source digest: sha256:583744e8956f294682a551fc082f086b174b8d2b72652c21b1dd68f3933e7211
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||||
|
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/cetmix/cetmix-tower/tree/16.0/cetmix_tower_git"><img alt="cetmix/cetmix-tower" src="https://img.shields.io/badge/github-cetmix%2Fcetmix--tower-lightgray.png?logo=github" /></a></p>
|
||||||
|
<p>This module implements Git Management functionality for <a class="reference external" href="https://cetmix.com/tower">Cetmix
|
||||||
|
Tower</a>.</p>
|
||||||
|
<p>Please refer to the <a class="reference external" href="https://cetmix.com/tower">official
|
||||||
|
documentation</a> for detailed information.</p>
|
||||||
|
<p><strong>Table of contents</strong></p>
|
||||||
|
<div class="contents local topic" id="contents">
|
||||||
|
<ul class="simple">
|
||||||
|
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
|
||||||
|
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a></li>
|
||||||
|
<li><a class="reference internal" href="#changelog" id="toc-entry-3">Changelog</a><ul>
|
||||||
|
<li><a class="reference internal" href="#section-1" id="toc-entry-4">16.0.2.0.1 (2025-12-11)</a></li>
|
||||||
|
<li><a class="reference internal" href="#section-2" id="toc-entry-5">16.0.2.0.0 (2025-10-27)</a></li>
|
||||||
|
<li><a class="reference internal" href="#section-3" id="toc-entry-6">16.0.1.0.6 (2025-08-18)</a></li>
|
||||||
|
<li><a class="reference internal" href="#section-4" id="toc-entry-7">16.0.1.0.5 (2025-08-17)</a></li>
|
||||||
|
<li><a class="reference internal" href="#section-5" id="toc-entry-8">16.0.1.0.4 (2025-07-29)</a></li>
|
||||||
|
<li><a class="reference internal" href="#section-6" id="toc-entry-9">16.0.1.0.3 (2025-05-23)</a></li>
|
||||||
|
<li><a class="reference internal" href="#section-7" id="toc-entry-10">16.0.1.0.2 (2025-05-16)</a></li>
|
||||||
|
<li><a class="reference internal" href="#section-8" id="toc-entry-11">16.0.1.0.1 (2025-05-09)</a></li>
|
||||||
|
<li><a class="reference internal" href="#section-9" id="toc-entry-12">16.0.1.0.0</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-13">Bug Tracker</a></li>
|
||||||
|
<li><a class="reference internal" href="#credits" id="toc-entry-14">Credits</a><ul>
|
||||||
|
<li><a class="reference internal" href="#authors" id="toc-entry-15">Authors</a></li>
|
||||||
|
<li><a class="reference internal" href="#maintainers" id="toc-entry-16">Maintainers</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="configuration">
|
||||||
|
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
|
||||||
|
<p>Please refer to the <a class="reference external" href="https://cetmix.com/tower">official
|
||||||
|
documentation</a> for detailed configuration
|
||||||
|
instructions.</p>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="usage">
|
||||||
|
<h1><a class="toc-backref" href="#toc-entry-2">Usage</a></h1>
|
||||||
|
<p>Please refer to the <a class="reference external" href="https://cetmix.com/tower">official
|
||||||
|
documentation</a> for detailed usage
|
||||||
|
instructions.</p>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="changelog">
|
||||||
|
<h1><a class="toc-backref" href="#toc-entry-3">Changelog</a></h1>
|
||||||
|
<div class="section" id="section-1">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-4">16.0.2.0.1 (2025-12-11)</a></h2>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Features: Improve search views, implement the search panel for
|
||||||
|
selected views. (5139)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="section-2">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-5">16.0.2.0.0 (2025-10-27)</a></h2>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Features: Major refactoring: implement Git repository entity. (4914)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="section-3">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-6">16.0.1.0.6 (2025-08-18)</a></h2>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Features: Link or copy a git project when uploading the linked file
|
||||||
|
using command (4759)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="section-4">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-7">16.0.1.0.5 (2025-08-17)</a></h2>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Features: Search servers by git reference (4838)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="section-5">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-8">16.0.1.0.4 (2025-07-29)</a></h2>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Features: Export related commands and flight plans together with
|
||||||
|
server (4849)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="section-6">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-9">16.0.1.0.3 (2025-05-23)</a></h2>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Bugfixes: Duplicated file is created when importing a YAML file with a
|
||||||
|
git project. (4715)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="section-7">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-10">16.0.1.0.2 (2025-05-16)</a></h2>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Features: Record references for git relations. (4670)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="section-8">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-11">16.0.1.0.1 (2025-05-09)</a></h2>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Bugfixes: Non-critical issues and performance improvements. (4663)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="section-9">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-12">16.0.1.0.0</a></h2>
|
||||||
|
<p>Release for Odoo 16.0</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="bug-tracker">
|
||||||
|
<h1><a class="toc-backref" href="#toc-entry-13">Bug Tracker</a></h1>
|
||||||
|
<p>Bugs are tracked on <a class="reference external" href="https://github.com/cetmix/cetmix-tower/issues">GitHub Issues</a>.
|
||||||
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
|
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||||
|
<a class="reference external" href="https://github.com/cetmix/cetmix-tower/issues/new?body=module:%20cetmix_tower_git%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||||
|
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="credits">
|
||||||
|
<h1><a class="toc-backref" href="#toc-entry-14">Credits</a></h1>
|
||||||
|
<div class="section" id="authors">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-15">Authors</a></h2>
|
||||||
|
<ul class="simple">
|
||||||
|
<li>Cetmix</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="maintainers">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-16">Maintainers</a></h2>
|
||||||
|
<p>This module is part of the <a class="reference external" href="https://github.com/cetmix/cetmix-tower/tree/16.0/cetmix_tower_git">cetmix/cetmix-tower</a> project on GitHub.</p>
|
||||||
|
<p>You are welcome to contribute.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
addons/cetmix_tower_git/tests/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from . import test_remote
|
||||||
|
from . import test_source
|
||||||
|
from . import test_project
|
||||||
|
from . import test_file_rel
|
||||||
|
from . import test_file_template_rel
|
||||||
|
from . import test_server
|
||||||
|
from . import test_repo
|
||||||
136
addons/cetmix_tower_git/tests/common.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon
|
||||||
|
|
||||||
|
|
||||||
|
class CommonTest(TestTowerCommon):
|
||||||
|
"""Common test class for all tests."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
# Models
|
||||||
|
cls.GitProject = cls.env["cx.tower.git.project"]
|
||||||
|
cls.GitProjectRel = cls.env["cx.tower.git.project.rel"]
|
||||||
|
cls.GitProjectFileTemplateRel = cls.env[
|
||||||
|
"cx.tower.git.project.file.template.rel"
|
||||||
|
]
|
||||||
|
cls.GitSource = cls.env["cx.tower.git.source"]
|
||||||
|
cls.GitRemote = cls.env["cx.tower.git.remote"]
|
||||||
|
|
||||||
|
# Data
|
||||||
|
# Project
|
||||||
|
cls.git_project_1 = cls.GitProject.create({"name": "Git Project 1"})
|
||||||
|
|
||||||
|
# Sources
|
||||||
|
cls.git_source_1 = cls.GitSource.create(
|
||||||
|
{"name": "Git Source 1", "git_project_id": cls.git_project_1.id}
|
||||||
|
)
|
||||||
|
cls.git_source_2 = cls.GitSource.create(
|
||||||
|
{"name": "Git Source 2", "git_project_id": cls.git_project_1.id}
|
||||||
|
)
|
||||||
|
# Repositories
|
||||||
|
cls.Repo = cls.env["cx.tower.git.repo"]
|
||||||
|
cls.RepoOwner = cls.env["cx.tower.git.repo.owner"]
|
||||||
|
|
||||||
|
cls.repo_cetmix_tower = cls.Repo.create(
|
||||||
|
{
|
||||||
|
"name": "Cetmix Tower",
|
||||||
|
"url": "https://github.com/cetmix-test/cetmix-tower-test.git",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.repo_oca_web = cls.Repo.create(
|
||||||
|
{
|
||||||
|
"name": "OCA Web",
|
||||||
|
"url": "https://github.com/oca-test/web-test.git",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.repo_odoo_enterprise = cls.Repo.create(
|
||||||
|
{
|
||||||
|
"name": "Odoo Enterprise",
|
||||||
|
"url": "https://github.com/odoo-test/enterprise-test.git",
|
||||||
|
"is_private": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.repo_gitlab_private = cls.Repo.create(
|
||||||
|
{
|
||||||
|
"name": "GitLab Private",
|
||||||
|
"url": "git@my.gitlab.com:cetmix-test/cetmix-tower-test.git",
|
||||||
|
"is_private": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.repo_bitbucket_private = cls.Repo.create(
|
||||||
|
{
|
||||||
|
"name": "Bitbucket Private",
|
||||||
|
"url": "https://bitbucket.com/cetmix-test/cetmix-tower-test-enterprise.git",
|
||||||
|
"is_private": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Same urls, different protocols (intentionally aliased)
|
||||||
|
cls.repo_other_ssh = cls.Repo.create(
|
||||||
|
{"url": "git@memegit.com:cetmix-test/cetmix-tower-test.git"}
|
||||||
|
)
|
||||||
|
cls.repo_other_https = cls.repo_other_ssh
|
||||||
|
|
||||||
|
# Remotes
|
||||||
|
cls.remote_github_https = cls.GitRemote.create(
|
||||||
|
{
|
||||||
|
"repo_id": cls.repo_cetmix_tower.id,
|
||||||
|
"source_id": cls.git_source_1.id,
|
||||||
|
"head_type": "pr",
|
||||||
|
"head": "https://github.com/cetmix-test/cetmix-tower-test/pull/123",
|
||||||
|
"sequence": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.remote_gitlab_https = cls.GitRemote.create(
|
||||||
|
{
|
||||||
|
"repo_id": cls.repo_gitlab_private.id,
|
||||||
|
"source_id": cls.git_source_1.id,
|
||||||
|
"head_type": "branch",
|
||||||
|
"head": "main",
|
||||||
|
"sequence": 2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.remote_gitlab_ssh = cls.GitRemote.create(
|
||||||
|
{
|
||||||
|
"repo_id": cls.repo_gitlab_private.id,
|
||||||
|
"source_id": cls.git_source_1.id,
|
||||||
|
"head_type": "commit",
|
||||||
|
"url_protocol": "ssh",
|
||||||
|
"head": "10000000",
|
||||||
|
"sequence": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.remote_bitbucket_https = cls.GitRemote.create(
|
||||||
|
{
|
||||||
|
"repo_id": cls.repo_bitbucket_private.id,
|
||||||
|
"source_id": cls.git_source_2.id,
|
||||||
|
"head_type": "branch",
|
||||||
|
"head": "dev",
|
||||||
|
"sequence": 4,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.remote_other_ssh = cls.GitRemote.create(
|
||||||
|
{
|
||||||
|
"repo_id": cls.repo_other_ssh.id,
|
||||||
|
"source_id": cls.git_source_2.id,
|
||||||
|
"head_type": "branch",
|
||||||
|
"url_protocol": "ssh",
|
||||||
|
"head": "old",
|
||||||
|
"sequence": 5,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# File
|
||||||
|
cls.server_1_file_1 = cls.File.create(
|
||||||
|
{
|
||||||
|
"name": "File 1",
|
||||||
|
"server_id": cls.server_test_1.id,
|
||||||
|
"source": "tower",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.file_template_1 = cls.FileTemplate.create(
|
||||||
|
{
|
||||||
|
"name": "File Template 1",
|
||||||
|
}
|
||||||
|
)
|
||||||
390
addons/cetmix_tower_git/tests/test_file_rel.py
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
from odoo.exceptions import AccessError
|
||||||
|
|
||||||
|
from .common import CommonTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileRel(CommonTest):
|
||||||
|
"""Test class for git file relation."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.file_1_rel = self.GitProjectRel.create(
|
||||||
|
{
|
||||||
|
"server_id": self.server_test_1.id,
|
||||||
|
"file_id": self.server_1_file_1.id,
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_file_rel_create(self):
|
||||||
|
"""Test if file relation is created correctly"""
|
||||||
|
|
||||||
|
# -- 1 --
|
||||||
|
# Check if file content is updated
|
||||||
|
|
||||||
|
# Get code from project
|
||||||
|
yaml_code_from_project = (
|
||||||
|
self.file_1_rel.git_project_id._generate_code_git_aggregator(
|
||||||
|
self.file_1_rel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.server_1_file_1.code,
|
||||||
|
yaml_code_from_project,
|
||||||
|
"File content is not updated correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check specific if remote is present in file
|
||||||
|
self.assertIn(
|
||||||
|
self.remote_other_ssh.repo_id.url_ssh,
|
||||||
|
self.server_1_file_1.code,
|
||||||
|
"Remote is not present in file",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 2 --
|
||||||
|
# Modify remove and check if file content is updated
|
||||||
|
self.remote_other_ssh.repo_id = self.Repo.create(
|
||||||
|
{
|
||||||
|
"url": "https://github.com/cetmix/cetmix-memes.git",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.remote_other_ssh.url_protocol = "https"
|
||||||
|
|
||||||
|
# Must be different from previous project code
|
||||||
|
self.assertNotEqual(
|
||||||
|
self.server_1_file_1.code,
|
||||||
|
yaml_code_from_project,
|
||||||
|
"File content is not updated correctly",
|
||||||
|
)
|
||||||
|
# New remote must be present in file
|
||||||
|
self.assertIn(
|
||||||
|
"https://github.com/cetmix/cetmix-memes.git",
|
||||||
|
self.server_1_file_1.code,
|
||||||
|
"Remote is not present in file",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 3 --
|
||||||
|
# Disable source and check if file content is updated
|
||||||
|
self.git_source_2.active = False
|
||||||
|
self.assertNotIn(
|
||||||
|
"https://github.com/cetmix/cetmix-memes.git",
|
||||||
|
self.server_1_file_1.code,
|
||||||
|
"Remote is present in file",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_format_git_aggregator(self):
|
||||||
|
"""Test if format git aggregator works correctly"""
|
||||||
|
|
||||||
|
# -- 1 --
|
||||||
|
# Check if YAML code is generated correctly
|
||||||
|
|
||||||
|
yaml_code = """# This file is generated with Cetmix Tower https://cetmix.com/tower
|
||||||
|
# It's designed to be used with git-aggregator tool developed by Acsone.
|
||||||
|
# Documentation for git-aggregator: https://github.com/acsone/git-aggregator
|
||||||
|
|
||||||
|
# You need to set the following variables in your environment:
|
||||||
|
# BITBUCKET_TOKEN, GITLAB_TOKEN, GITLAB_TOKEN_NAME
|
||||||
|
# and run git-aggregator with '--expand-env' parameter.
|
||||||
|
|
||||||
|
./git_project_1_git_source_1:
|
||||||
|
remotes:
|
||||||
|
remote_1: https://github.com/cetmix-test/cetmix-tower-test.git
|
||||||
|
remote_2: https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git
|
||||||
|
remote_3: git@my.gitlab.com:cetmix-test/cetmix-tower-test.git
|
||||||
|
merges:
|
||||||
|
- remote: remote_1
|
||||||
|
ref: refs/pull/123/head
|
||||||
|
- remote: remote_2
|
||||||
|
ref: main
|
||||||
|
- remote: remote_3
|
||||||
|
ref: '10000000'
|
||||||
|
target: remote_1
|
||||||
|
./git_project_1_git_source_1_2:
|
||||||
|
remotes:
|
||||||
|
remote_1: https://x-token-auth:$BITBUCKET_TOKEN@bitbucket.com/cetmix-test/cetmix-tower-test-enterprise.git
|
||||||
|
remote_2: git@memegit.com:cetmix-test/cetmix-tower-test.git
|
||||||
|
merges:
|
||||||
|
- remote: remote_1
|
||||||
|
ref: dev
|
||||||
|
- remote: remote_2
|
||||||
|
ref: old
|
||||||
|
target: remote_1
|
||||||
|
""" # noqa: E501
|
||||||
|
|
||||||
|
# Get code from project
|
||||||
|
yaml_code_from_project = (
|
||||||
|
self.file_1_rel.git_project_id._generate_code_git_aggregator(
|
||||||
|
self.file_1_rel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
yaml_code_from_project,
|
||||||
|
yaml_code,
|
||||||
|
"YAML code is not generated correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 2 --
|
||||||
|
# Unlink remote and check if file content is updated
|
||||||
|
self.remote_github_https.unlink()
|
||||||
|
yaml_code_from_project = (
|
||||||
|
self.file_1_rel.git_project_id._generate_code_git_aggregator(
|
||||||
|
self.file_1_rel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
yaml_code = """# This file is generated with Cetmix Tower https://cetmix.com/tower
|
||||||
|
# It's designed to be used with git-aggregator tool developed by Acsone.
|
||||||
|
# Documentation for git-aggregator: https://github.com/acsone/git-aggregator
|
||||||
|
|
||||||
|
# You need to set the following variables in your environment:
|
||||||
|
# BITBUCKET_TOKEN, GITLAB_TOKEN, GITLAB_TOKEN_NAME
|
||||||
|
# and run git-aggregator with '--expand-env' parameter.
|
||||||
|
|
||||||
|
./git_project_1_git_source_1:
|
||||||
|
remotes:
|
||||||
|
remote_2: https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git
|
||||||
|
remote_3: git@my.gitlab.com:cetmix-test/cetmix-tower-test.git
|
||||||
|
merges:
|
||||||
|
- remote: remote_2
|
||||||
|
ref: main
|
||||||
|
- remote: remote_3
|
||||||
|
ref: '10000000'
|
||||||
|
target: remote_2
|
||||||
|
./git_project_1_git_source_1_2:
|
||||||
|
remotes:
|
||||||
|
remote_1: https://x-token-auth:$BITBUCKET_TOKEN@bitbucket.com/cetmix-test/cetmix-tower-test-enterprise.git
|
||||||
|
remote_2: git@memegit.com:cetmix-test/cetmix-tower-test.git
|
||||||
|
merges:
|
||||||
|
- remote: remote_1
|
||||||
|
ref: dev
|
||||||
|
- remote: remote_2
|
||||||
|
ref: old
|
||||||
|
target: remote_1
|
||||||
|
""" # noqa: E501
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
yaml_code_from_project,
|
||||||
|
yaml_code,
|
||||||
|
"YAML code is not generated correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 3 --
|
||||||
|
# Unlink source and check if file content is updated
|
||||||
|
self.git_source_2.unlink()
|
||||||
|
yaml_code_from_project = (
|
||||||
|
self.file_1_rel.git_project_id._generate_code_git_aggregator(
|
||||||
|
self.file_1_rel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
yaml_code = """# This file is generated with Cetmix Tower https://cetmix.com/tower
|
||||||
|
# It's designed to be used with git-aggregator tool developed by Acsone.
|
||||||
|
# Documentation for git-aggregator: https://github.com/acsone/git-aggregator
|
||||||
|
|
||||||
|
# You need to set the following variables in your environment:
|
||||||
|
# GITLAB_TOKEN, GITLAB_TOKEN_NAME
|
||||||
|
# and run git-aggregator with '--expand-env' parameter.
|
||||||
|
|
||||||
|
./git_project_1_git_source_1:
|
||||||
|
remotes:
|
||||||
|
remote_2: https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git
|
||||||
|
remote_3: git@my.gitlab.com:cetmix-test/cetmix-tower-test.git
|
||||||
|
merges:
|
||||||
|
- remote: remote_2
|
||||||
|
ref: main
|
||||||
|
- remote: remote_3
|
||||||
|
ref: '10000000'
|
||||||
|
target: remote_2
|
||||||
|
""" # noqa: E501
|
||||||
|
self.assertEqual(
|
||||||
|
yaml_code_from_project,
|
||||||
|
yaml_code,
|
||||||
|
"YAML code is not generated correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_access(self):
|
||||||
|
"""Test that regular users have no access to git project relations"""
|
||||||
|
user_rel = self.GitProjectRel.with_user(self.user)
|
||||||
|
|
||||||
|
# Try create - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_rel.create(
|
||||||
|
{
|
||||||
|
"server_id": self.server_test_1.id,
|
||||||
|
"file_id": self.server_1_file_1.id,
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try read - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_rel.browse(self.file_1_rel.id).read(["name"])
|
||||||
|
|
||||||
|
# Try write - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_rel.browse(self.file_1_rel.id).write(
|
||||||
|
{"project_format": "git_aggregator"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try unlink - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_rel.browse(self.file_1_rel.id).unlink()
|
||||||
|
|
||||||
|
def test_manager_read_access(self):
|
||||||
|
"""Test manager read access rules"""
|
||||||
|
manager_rel = self.GitProjectRel.with_user(self.manager)
|
||||||
|
|
||||||
|
# Initially manager should not have access
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_rel.browse(self.file_1_rel.id).read(["name"])
|
||||||
|
|
||||||
|
# Add manager as project user - should have read access
|
||||||
|
self.git_project_1.write({"user_ids": [(4, self.manager.id)]})
|
||||||
|
self.assertEqual(manager_rel.browse(self.file_1_rel.id).name, "Git Project 1")
|
||||||
|
|
||||||
|
# Remove from project, add as server user - should have read access
|
||||||
|
self.git_project_1.write({"user_ids": [(3, self.manager.id)]})
|
||||||
|
self.server_test_1.write({"user_ids": [(4, self.manager.id)]})
|
||||||
|
self.assertEqual(manager_rel.browse(self.file_1_rel.id).name, "Git Project 1")
|
||||||
|
|
||||||
|
# Remove from server users, add as project manager - should have read access
|
||||||
|
self.server_test_1.write({"user_ids": [(3, self.manager.id)]})
|
||||||
|
self.git_project_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
self.assertEqual(manager_rel.browse(self.file_1_rel.id).name, "Git Project 1")
|
||||||
|
|
||||||
|
# Remove from project, add as server manager - should have read access
|
||||||
|
self.git_project_1.write({"manager_ids": [(3, self.manager.id)]})
|
||||||
|
self.server_test_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
self.assertEqual(manager_rel.browse(self.file_1_rel.id).name, "Git Project 1")
|
||||||
|
|
||||||
|
def test_manager_write_access(self):
|
||||||
|
"""Test manager write/create access rules"""
|
||||||
|
manager_rel = self.GitProjectRel.with_user(self.manager)
|
||||||
|
|
||||||
|
# Create new file to avoid unique constraint violation
|
||||||
|
file_2 = self.File.create(
|
||||||
|
{
|
||||||
|
"name": "test_file_2",
|
||||||
|
"server_id": self.server_test_1.id,
|
||||||
|
"source": "tower",
|
||||||
|
"file_type": "text",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try create without being project and server manager - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_rel.create(
|
||||||
|
{
|
||||||
|
"server_id": self.server_test_1.id,
|
||||||
|
"file_id": file_2.id,
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add as project manager only - should still fail
|
||||||
|
file_3 = self.File.create(
|
||||||
|
{
|
||||||
|
"name": "test_file_3",
|
||||||
|
"server_id": self.server_test_1.id,
|
||||||
|
"source": "tower",
|
||||||
|
"file_type": "text",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.git_project_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_rel.create(
|
||||||
|
{
|
||||||
|
"server_id": self.server_test_1.id,
|
||||||
|
"file_id": file_3.id,
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add as server manager - should succeed
|
||||||
|
file_4 = self.File.create(
|
||||||
|
{
|
||||||
|
"name": "test_file_4",
|
||||||
|
"server_id": self.server_test_1.id,
|
||||||
|
"source": "tower",
|
||||||
|
"file_type": "text",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.server_test_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
rel = manager_rel.create(
|
||||||
|
{
|
||||||
|
"server_id": self.server_test_1.id,
|
||||||
|
"file_id": file_4.id,
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(rel.exists())
|
||||||
|
|
||||||
|
# Test write access
|
||||||
|
rel.write({"project_format": "git_aggregator"})
|
||||||
|
|
||||||
|
# Remove server manager access - should fail to write
|
||||||
|
self.server_test_1.write({"manager_ids": [(3, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
rel.write({"project_format": "git_aggregator"})
|
||||||
|
|
||||||
|
# Remove project manager access - should fail to write
|
||||||
|
self.git_project_1.write({"manager_ids": [(3, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
rel.write({"project_format": "git_aggregator"})
|
||||||
|
|
||||||
|
def test_manager_unlink_access(self):
|
||||||
|
"""Test manager unlink access rules"""
|
||||||
|
manager_rel = self.GitProjectRel.with_user(self.manager)
|
||||||
|
|
||||||
|
# Try delete without being project and server manager - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_rel.browse(self.file_1_rel.id).unlink()
|
||||||
|
|
||||||
|
# Add as project manager only - should fail
|
||||||
|
self.git_project_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_rel.browse(self.file_1_rel.id).unlink()
|
||||||
|
|
||||||
|
# Add as server manager - should succeed
|
||||||
|
self.server_test_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
self.file_1_rel.unlink()
|
||||||
|
self.assertFalse(self.file_1_rel.exists())
|
||||||
|
|
||||||
|
def test_root_access(self):
|
||||||
|
"""Test root access rules"""
|
||||||
|
root_rel = self.GitProjectRel.with_user(self.root)
|
||||||
|
|
||||||
|
# Create new file to avoid unique constraint violation
|
||||||
|
file_3 = self.File.create(
|
||||||
|
{
|
||||||
|
"name": "test_file_3",
|
||||||
|
"server_id": self.server_test_1.id,
|
||||||
|
"source": "tower",
|
||||||
|
"file_type": "text",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create - should succeed
|
||||||
|
rel = root_rel.create(
|
||||||
|
{
|
||||||
|
"server_id": self.server_test_1.id,
|
||||||
|
"file_id": file_3.id,
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(rel.exists())
|
||||||
|
|
||||||
|
# Read - should succeed
|
||||||
|
self.assertEqual(root_rel.browse(rel.id).name, "Git Project 1")
|
||||||
|
|
||||||
|
# Write - should succeed
|
||||||
|
root_rel.browse(rel.id).write({"project_format": "git_aggregator"})
|
||||||
|
|
||||||
|
# Delete - should succeed
|
||||||
|
rel.unlink()
|
||||||
|
self.assertFalse(rel.exists())
|
||||||
308
addons/cetmix_tower_git/tests/test_file_template_rel.py
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
from odoo.exceptions import AccessError
|
||||||
|
|
||||||
|
from .common import CommonTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileTemplateRel(CommonTest):
|
||||||
|
"""Test class for git file template relation."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.file_template_1_rel = cls.GitProjectFileTemplateRel.create(
|
||||||
|
{
|
||||||
|
"git_project_id": cls.git_project_1.id,
|
||||||
|
"file_template_id": cls.file_template_1.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_file_template_rel_create(self):
|
||||||
|
"""Test if file template relation is created correctly"""
|
||||||
|
|
||||||
|
# -- 1 --
|
||||||
|
# Check if file content is updated
|
||||||
|
|
||||||
|
# Get code from project
|
||||||
|
yaml_code_from_project = (
|
||||||
|
self.file_template_1_rel.git_project_id._generate_code_git_aggregator(
|
||||||
|
self.file_template_1_rel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.file_template_1.code,
|
||||||
|
yaml_code_from_project,
|
||||||
|
"File template content is not updated correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check specific if remote is present in file
|
||||||
|
self.assertIn(
|
||||||
|
self.remote_other_ssh.repo_id.url_ssh,
|
||||||
|
self.file_template_1.code,
|
||||||
|
"Remote is not present in file template",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 2 --
|
||||||
|
# Modify remove and check if file template content is updated
|
||||||
|
self.remote_other_ssh.repo_id = self.Repo.create(
|
||||||
|
{
|
||||||
|
"url": "https://github.com/cetmix/cetmix-memes.git",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.remote_other_ssh.url_protocol = "https"
|
||||||
|
|
||||||
|
# Must be different from previous project code
|
||||||
|
self.assertNotEqual(
|
||||||
|
self.file_template_1.code,
|
||||||
|
yaml_code_from_project,
|
||||||
|
"File template content is not updated correctly",
|
||||||
|
)
|
||||||
|
# New remote must be present in file
|
||||||
|
self.assertIn(
|
||||||
|
"https://github.com/cetmix/cetmix-memes.git",
|
||||||
|
self.file_template_1.code,
|
||||||
|
"Remote is not present in file template",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 3 --
|
||||||
|
# Disable source and check if file content is updated
|
||||||
|
self.git_source_2.active = False
|
||||||
|
self.assertNotIn(
|
||||||
|
"https://github.com/cetmix/cetmix-memes.git",
|
||||||
|
self.file_template_1.code,
|
||||||
|
"Remote is present in file template",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_format_git_aggregator(self):
|
||||||
|
"""Test if format git aggregator works correctly"""
|
||||||
|
|
||||||
|
# -- 1 --
|
||||||
|
# Check if YAML code is generated correctly
|
||||||
|
|
||||||
|
yaml_code = """# This file is generated with Cetmix Tower https://cetmix.com/tower
|
||||||
|
# It's designed to be used with git-aggregator tool developed by Acsone.
|
||||||
|
# Documentation for git-aggregator: https://github.com/acsone/git-aggregator
|
||||||
|
|
||||||
|
# You need to set the following variables in your environment:
|
||||||
|
# BITBUCKET_TOKEN, GITLAB_TOKEN, GITLAB_TOKEN_NAME
|
||||||
|
# and run git-aggregator with '--expand-env' parameter.
|
||||||
|
|
||||||
|
./git_project_1_git_source_1:
|
||||||
|
remotes:
|
||||||
|
remote_1: https://github.com/cetmix-test/cetmix-tower-test.git
|
||||||
|
remote_2: https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git
|
||||||
|
remote_3: git@my.gitlab.com:cetmix-test/cetmix-tower-test.git
|
||||||
|
merges:
|
||||||
|
- remote: remote_1
|
||||||
|
ref: refs/pull/123/head
|
||||||
|
- remote: remote_2
|
||||||
|
ref: main
|
||||||
|
- remote: remote_3
|
||||||
|
ref: '10000000'
|
||||||
|
target: remote_1
|
||||||
|
./git_project_1_git_source_1_2:
|
||||||
|
remotes:
|
||||||
|
remote_1: https://x-token-auth:$BITBUCKET_TOKEN@bitbucket.com/cetmix-test/cetmix-tower-test-enterprise.git
|
||||||
|
remote_2: git@memegit.com:cetmix-test/cetmix-tower-test.git
|
||||||
|
merges:
|
||||||
|
- remote: remote_1
|
||||||
|
ref: dev
|
||||||
|
- remote: remote_2
|
||||||
|
ref: old
|
||||||
|
target: remote_1
|
||||||
|
""" # noqa: E501
|
||||||
|
|
||||||
|
# Get code from project
|
||||||
|
yaml_code_from_project = (
|
||||||
|
self.file_template_1_rel.git_project_id._generate_code_git_aggregator(
|
||||||
|
self.file_template_1_rel
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
yaml_code_from_project,
|
||||||
|
yaml_code,
|
||||||
|
"YAML code is not generated correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_access(self):
|
||||||
|
"""Test that regular users have no access to git project relations"""
|
||||||
|
user_rel = self.GitProjectFileTemplateRel.with_user(self.user)
|
||||||
|
|
||||||
|
# Try create - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_rel.create(
|
||||||
|
{
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"file_template_id": self.file_template_1.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try read - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_rel.browse(self.file_template_1_rel.id).read(["name"])
|
||||||
|
|
||||||
|
# Try write - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_rel.browse(self.file_template_1_rel.id).write(
|
||||||
|
{"project_format": "git_aggregator"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try unlink - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_rel.browse(self.file_template_1_rel.id).unlink()
|
||||||
|
|
||||||
|
def test_manager_read_access(self):
|
||||||
|
"""Test manager read access rules"""
|
||||||
|
manager_rel = self.GitProjectFileTemplateRel.with_user(self.manager)
|
||||||
|
|
||||||
|
# Initially manager should not have access
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_rel.browse(self.file_template_1_rel.id).read(["name"])
|
||||||
|
|
||||||
|
# Add manager as project user - should have read access
|
||||||
|
self.git_project_1.write({"user_ids": [(4, self.manager.id)]})
|
||||||
|
self.assertEqual(
|
||||||
|
manager_rel.browse(self.file_template_1_rel.id).name, "Git Project 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove from project, add as file template user
|
||||||
|
# should have read access
|
||||||
|
self.git_project_1.write({"user_ids": [(3, self.manager.id)]})
|
||||||
|
self.file_template_1.write({"user_ids": [(4, self.manager.id)]})
|
||||||
|
self.assertEqual(
|
||||||
|
manager_rel.browse(self.file_template_1_rel.id).name, "Git Project 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove from file template users, add as project manager
|
||||||
|
# should have read access
|
||||||
|
self.file_template_1.write({"user_ids": [(3, self.manager.id)]})
|
||||||
|
self.git_project_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
self.assertEqual(
|
||||||
|
manager_rel.browse(self.file_template_1_rel.id).name, "Git Project 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove from project, add as file template manager
|
||||||
|
# should have read access
|
||||||
|
self.git_project_1.write({"manager_ids": [(3, self.manager.id)]})
|
||||||
|
self.file_template_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
self.assertEqual(
|
||||||
|
manager_rel.browse(self.file_template_1_rel.id).name, "Git Project 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_manager_write_access(self):
|
||||||
|
"""Test manager write/create access rules"""
|
||||||
|
manager_rel = self.GitProjectFileTemplateRel.with_user(self.manager)
|
||||||
|
|
||||||
|
# Create new file template to avoid unique constraint violation
|
||||||
|
file_template_2 = self.FileTemplate.create(
|
||||||
|
{
|
||||||
|
"name": "test_file_template_2",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try create without being project and file template manager - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_rel.create(
|
||||||
|
{
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"file_template_id": file_template_2.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add as project manager only - should still fail
|
||||||
|
file_template_3 = self.FileTemplate.create(
|
||||||
|
{
|
||||||
|
"name": "test_file_template_3",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.git_project_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_rel.create(
|
||||||
|
{
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"file_template_id": file_template_3.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add as file template manager - should succeed
|
||||||
|
file_template_4 = self.FileTemplate.create(
|
||||||
|
{
|
||||||
|
"name": "test_file_template_4",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
file_template_4.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
rel = manager_rel.create(
|
||||||
|
{
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"file_template_id": file_template_4.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(rel.exists())
|
||||||
|
|
||||||
|
# Test write access
|
||||||
|
rel.write({"project_format": "git_aggregator"})
|
||||||
|
|
||||||
|
# Remove file template manager access - should fail to write
|
||||||
|
file_template_4.write({"manager_ids": [(3, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
rel.write({"project_format": "git_aggregator"})
|
||||||
|
|
||||||
|
# Remove project manager access - should fail to write
|
||||||
|
self.git_project_1.write({"manager_ids": [(3, self.manager.id)]})
|
||||||
|
file_template_4.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
rel.write({"project_format": "git_aggregator"})
|
||||||
|
|
||||||
|
def test_manager_unlink_access(self):
|
||||||
|
"""Test manager unlink access rules"""
|
||||||
|
manager_rel = self.GitProjectFileTemplateRel.with_user(self.manager)
|
||||||
|
|
||||||
|
# Try delete without being project and server manager - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_rel.browse(self.file_template_1_rel.id).unlink()
|
||||||
|
|
||||||
|
# Add as project manager only - should fail
|
||||||
|
self.git_project_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_rel.browse(self.file_template_1_rel.id).unlink()
|
||||||
|
|
||||||
|
# Add as file template manager - should succeed
|
||||||
|
self.file_template_1.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
self.file_template_1_rel.unlink()
|
||||||
|
self.assertFalse(self.file_template_1_rel.exists())
|
||||||
|
|
||||||
|
def test_root_access(self):
|
||||||
|
"""Test root access rules"""
|
||||||
|
root_rel = self.GitProjectFileTemplateRel.with_user(self.root)
|
||||||
|
|
||||||
|
# Create new file to avoid unique constraint violation
|
||||||
|
file_template_3 = self.FileTemplate.create(
|
||||||
|
{
|
||||||
|
"name": "test_file_template_3",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create - should succeed
|
||||||
|
rel = root_rel.create(
|
||||||
|
{
|
||||||
|
"git_project_id": self.git_project_1.id,
|
||||||
|
"file_template_id": file_template_3.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(rel.exists())
|
||||||
|
|
||||||
|
# Read - should succeed
|
||||||
|
self.assertEqual(root_rel.browse(rel.id).name, "Git Project 1")
|
||||||
|
|
||||||
|
# Write - should succeed
|
||||||
|
root_rel.browse(rel.id).write({"project_format": "git_aggregator"})
|
||||||
|
|
||||||
|
# Delete - should succeed
|
||||||
|
rel.unlink()
|
||||||
|
self.assertFalse(rel.exists())
|
||||||
315
addons/cetmix_tower_git/tests/test_project.py
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
from odoo.exceptions import AccessError
|
||||||
|
|
||||||
|
from .common import CommonTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestProject(CommonTest):
|
||||||
|
"""Test class for git project."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
# Remove user bob from all groups
|
||||||
|
cls.remove_from_group(
|
||||||
|
cls.user_bob,
|
||||||
|
[
|
||||||
|
"cetmix_tower_server.group_user",
|
||||||
|
"cetmix_tower_server.group_manager",
|
||||||
|
"cetmix_tower_server.group_root",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create another manager for testing
|
||||||
|
cls.manager_2 = cls.Users.create(
|
||||||
|
{
|
||||||
|
"name": "Second Manager",
|
||||||
|
"login": "manager2",
|
||||||
|
"email": "manager2@test.com",
|
||||||
|
"groups_id": [(4, cls.env.ref("cetmix_tower_server.group_manager").id)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test project as root
|
||||||
|
cls.project = cls.GitProject.create(
|
||||||
|
{
|
||||||
|
"name": "Test Project",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_access(self):
|
||||||
|
"""Test that regular users have no access to git projects"""
|
||||||
|
user_project = self.GitProject.with_user(self.user)
|
||||||
|
|
||||||
|
# Test CRUD operations
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_project.create({"name": "New Project"})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_project.browse(self.project.id).read(["name"])
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_project.browse(self.project.id).write({"name": "Updated Name"})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_project.browse(self.project.id).unlink()
|
||||||
|
|
||||||
|
def test_manager_read_access(self):
|
||||||
|
"""Test manager read access rules"""
|
||||||
|
manager_project = self.GitProject.with_user(self.manager)
|
||||||
|
|
||||||
|
# Manager not in user_ids or manager_ids - should not read
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_project.browse(self.project.id).read(["name"])
|
||||||
|
|
||||||
|
# Add manager to user_ids - should read
|
||||||
|
self.project.write({"user_ids": [(4, self.manager.id)]})
|
||||||
|
self.assertEqual(manager_project.browse(self.project.id).name, "Test Project")
|
||||||
|
|
||||||
|
# Remove from user_ids, add to manager_ids - should read
|
||||||
|
self.project.write(
|
||||||
|
{"user_ids": [(3, self.manager.id)], "manager_ids": [(4, self.manager.id)]}
|
||||||
|
)
|
||||||
|
self.assertEqual(manager_project.browse(self.project.id).name, "Test Project")
|
||||||
|
|
||||||
|
def test_manager_write_access(self):
|
||||||
|
"""Test manager write/create access rules"""
|
||||||
|
manager_project = self.GitProject.with_user(self.manager)
|
||||||
|
|
||||||
|
# Create - should succeed as manager is added by default
|
||||||
|
new_project = manager_project.create({"name": "New Project"})
|
||||||
|
self.assertTrue(new_project.exists())
|
||||||
|
self.assertIn(self.manager, new_project.manager_ids)
|
||||||
|
|
||||||
|
# Write - not in manager_ids, should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_project.browse(self.project.id).write({"name": "Updated Name"})
|
||||||
|
|
||||||
|
# Add to manager_ids - should write
|
||||||
|
self.project.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
manager_project.browse(self.project.id).write({"name": "Updated Name"})
|
||||||
|
self.assertEqual(self.project.name, "Updated Name")
|
||||||
|
|
||||||
|
def test_manager_unlink_access(self):
|
||||||
|
"""Test manager unlink access rules"""
|
||||||
|
# Create project as manager_2
|
||||||
|
project = self.GitProject.with_user(self.manager_2).create(
|
||||||
|
{"name": "Project to Delete"}
|
||||||
|
)
|
||||||
|
manager_project = self.GitProject.with_user(self.manager)
|
||||||
|
|
||||||
|
# Try delete as different manager - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_project.browse(project.id).unlink()
|
||||||
|
|
||||||
|
# Add to manager_ids but not creator - should fail
|
||||||
|
project.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_project.browse(project.id).unlink()
|
||||||
|
|
||||||
|
# Create as manager and try delete - should succeed
|
||||||
|
own_project = manager_project.create({"name": "Own Project"})
|
||||||
|
self.assertTrue(own_project.exists())
|
||||||
|
own_project.unlink()
|
||||||
|
self.assertFalse(own_project.exists())
|
||||||
|
|
||||||
|
def test_root_access(self):
|
||||||
|
"""Test root access rules"""
|
||||||
|
root_project = self.GitProject.with_user(self.root)
|
||||||
|
|
||||||
|
# Create
|
||||||
|
new_project = root_project.create({"name": "Root Project"})
|
||||||
|
self.assertTrue(new_project.exists())
|
||||||
|
|
||||||
|
# Read
|
||||||
|
self.assertEqual(root_project.browse(self.project.id).name, "Test Project")
|
||||||
|
|
||||||
|
# Write
|
||||||
|
root_project.browse(self.project.id).write({"name": "Updated by Root"})
|
||||||
|
self.assertEqual(self.project.name, "Updated by Root")
|
||||||
|
|
||||||
|
# Delete
|
||||||
|
new_project.unlink()
|
||||||
|
self.assertFalse(new_project.exists())
|
||||||
|
|
||||||
|
def test_compute_user_ids(self):
|
||||||
|
"""Test computation of user_ids and manager_ids for git projects"""
|
||||||
|
# Add users "Bob" and "user" to the group "cetmix_tower_server.group_manager"
|
||||||
|
self.add_to_group(self.user_bob, "cetmix_tower_server.group_manager")
|
||||||
|
self.add_to_group(self.user, "cetmix_tower_server.group_manager")
|
||||||
|
|
||||||
|
# -- 1 --
|
||||||
|
# Create project as manager
|
||||||
|
project_as_manager = self.GitProject.with_user(self.manager).create(
|
||||||
|
{
|
||||||
|
"name": "Project As Manager",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Check that manager is added to both user_ids and manager_ids by default
|
||||||
|
self.assertEqual(len(project_as_manager.user_ids), 1)
|
||||||
|
self.assertIn(self.manager, project_as_manager.user_ids)
|
||||||
|
self.assertEqual(len(project_as_manager.manager_ids), 1)
|
||||||
|
self.assertIn(self.manager, project_as_manager.manager_ids)
|
||||||
|
|
||||||
|
# -- 2 --
|
||||||
|
# Create servers with multiple users and managers
|
||||||
|
server_1 = self.Server.create(
|
||||||
|
{
|
||||||
|
"name": "Test Server 1",
|
||||||
|
"ip_v4_address": "localhost",
|
||||||
|
"ssh_username": "admin",
|
||||||
|
"ssh_password": "password",
|
||||||
|
"os_id": self.os_debian_10.id,
|
||||||
|
"user_ids": [(6, 0, [self.user_bob.id, self.user.id])], # Two users
|
||||||
|
"manager_ids": [
|
||||||
|
(6, 0, [self.manager.id, self.manager_2.id])
|
||||||
|
], # Two managers
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
server_2 = self.Server.create(
|
||||||
|
{
|
||||||
|
"name": "Test Server 2",
|
||||||
|
"ip_v4_address": "localhost",
|
||||||
|
"ssh_username": "admin",
|
||||||
|
"ssh_password": "password",
|
||||||
|
"os_id": self.os_debian_10.id,
|
||||||
|
"user_ids": [
|
||||||
|
(6, 0, [self.user_bob.id, self.user.id])
|
||||||
|
], # Same two users
|
||||||
|
"manager_ids": [
|
||||||
|
(6, 0, [self.manager.id, self.manager_2.id])
|
||||||
|
], # Same two managers
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create project and link servers
|
||||||
|
project = self.GitProject.create(
|
||||||
|
{
|
||||||
|
"name": "Test Project",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create files and link them to the project
|
||||||
|
for server in [server_1, server_2]:
|
||||||
|
file = self.File.create(
|
||||||
|
{
|
||||||
|
"name": f"test_file_{server.name}",
|
||||||
|
"server_id": server.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.GitProjectRel.create(
|
||||||
|
{
|
||||||
|
"server_id": server.id,
|
||||||
|
"file_id": file.id,
|
||||||
|
"git_project_id": project.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Invalidate cache to ensure computed fields are updated
|
||||||
|
project.invalidate_recordset(["server_ids", "user_ids", "manager_ids"])
|
||||||
|
|
||||||
|
# -- 3 --
|
||||||
|
# Test computed values with linked servers
|
||||||
|
# Each user/manager should be counted only once even if present in both servers
|
||||||
|
self.assertEqual(len(project.server_ids), 2)
|
||||||
|
self.assertEqual(len(project.user_ids), 2) # Two unique users
|
||||||
|
self.assertIn(self.user_bob, project.user_ids)
|
||||||
|
self.assertIn(self.user, project.user_ids)
|
||||||
|
self.assertEqual(len(project.manager_ids), 2) # Two unique managers
|
||||||
|
self.assertIn(self.manager, project.manager_ids)
|
||||||
|
self.assertIn(self.manager_2, project.manager_ids)
|
||||||
|
|
||||||
|
# -- 4 --
|
||||||
|
# Add server with different users/managers
|
||||||
|
server_3 = self.Server.create(
|
||||||
|
{
|
||||||
|
"name": "Test Server 3",
|
||||||
|
"ip_v4_address": "localhost",
|
||||||
|
"ssh_username": "admin",
|
||||||
|
"ssh_password": "password",
|
||||||
|
"os_id": self.os_debian_10.id,
|
||||||
|
"user_ids": [(6, 0, [self.user_bob.id])], # Only one user
|
||||||
|
"manager_ids": [(6, 0, [self.manager_2.id])], # Only second manager
|
||||||
|
}
|
||||||
|
)
|
||||||
|
file_3 = self.File.create(
|
||||||
|
{
|
||||||
|
"name": "test_file_3",
|
||||||
|
"server_id": server_3.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.GitProjectRel.create(
|
||||||
|
{
|
||||||
|
"server_id": server_3.id,
|
||||||
|
"file_id": file_3.id,
|
||||||
|
"git_project_id": project.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Invalidate cache to ensure computed fields are updated
|
||||||
|
project.invalidate_recordset(["server_ids", "user_ids", "manager_ids"])
|
||||||
|
|
||||||
|
# Test that computed values are updated correctly
|
||||||
|
# Only users/managers present in all servers should remain
|
||||||
|
self.assertEqual(len(project.server_ids), 3)
|
||||||
|
self.assertEqual(len(project.user_ids), 1) # Only bob is in all servers
|
||||||
|
self.assertIn(self.user_bob, project.user_ids)
|
||||||
|
self.assertEqual(
|
||||||
|
len(project.manager_ids), 1
|
||||||
|
) # Only manager_2 is in all servers
|
||||||
|
self.assertIn(self.manager_2, project.manager_ids)
|
||||||
|
|
||||||
|
# -- 5 --
|
||||||
|
# Verify that first manager can still access the project
|
||||||
|
project_as_manager_1 = self.GitProject.with_user(self.manager).browse(
|
||||||
|
project.id
|
||||||
|
)
|
||||||
|
self.assertTrue(project_as_manager_1.exists())
|
||||||
|
self.assertEqual(project_as_manager_1.name, "Test Project")
|
||||||
|
|
||||||
|
def test_manager_server_based_access(self):
|
||||||
|
"""Test manager access through server relationships"""
|
||||||
|
manager_project = self.GitProject.with_user(self.manager)
|
||||||
|
|
||||||
|
# Create a server where manager is a user
|
||||||
|
server = self.Server.create(
|
||||||
|
{
|
||||||
|
"name": "Test Server",
|
||||||
|
"ip_v4_address": "localhost",
|
||||||
|
"ssh_username": "admin",
|
||||||
|
"ssh_password": "password",
|
||||||
|
"os_id": self.os_debian_10.id,
|
||||||
|
"user_ids": [(4, self.manager.id)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a file and link project to server
|
||||||
|
file = self.File.create(
|
||||||
|
{
|
||||||
|
"name": "test_file",
|
||||||
|
"server_id": server.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.GitProjectRel.create(
|
||||||
|
{
|
||||||
|
"server_id": server.id,
|
||||||
|
"file_id": file.id,
|
||||||
|
"git_project_id": self.project.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Manager should be able to read project through server relationship
|
||||||
|
self.assertEqual(manager_project.browse(self.project.id).name, "Test Project")
|
||||||
|
|
||||||
|
# Remove manager from server users
|
||||||
|
server.write({"user_ids": [(3, self.manager.id)]})
|
||||||
|
|
||||||
|
# Manager should not be able to read project anymore
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_project.browse(self.project.id).read(["name"])
|
||||||
|
|
||||||
|
# Add manager to server managers
|
||||||
|
server.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
|
||||||
|
# Manager should be able to read project again
|
||||||
|
self.assertEqual(manager_project.browse(self.project.id).name, "Test Project")
|
||||||
462
addons/cetmix_tower_git/tests/test_remote.py
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
from odoo.exceptions import AccessError
|
||||||
|
|
||||||
|
from .common import CommonTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestRemote(CommonTest):
|
||||||
|
"""Test class for git remote."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
# Create another manager for testing
|
||||||
|
cls.manager_2 = cls.Users.create(
|
||||||
|
{
|
||||||
|
"name": "Second Manager",
|
||||||
|
"login": "manager2",
|
||||||
|
"email": "manager2@test.com",
|
||||||
|
"groups_id": [(4, cls.env.ref("cetmix_tower_server.group_manager").id)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test project and source as root
|
||||||
|
cls.project = cls.GitProject.create(
|
||||||
|
{
|
||||||
|
"name": "Test Project",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.source = cls.GitSource.create(
|
||||||
|
{
|
||||||
|
"name": "Test Source",
|
||||||
|
"git_project_id": cls.project.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.repo_cetmix_tower = cls.Repo.create(
|
||||||
|
{
|
||||||
|
"name": "Cetmix Tower",
|
||||||
|
"url": "https://github.com/cetmix-test/cetmix-tower.git",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.remote = cls.GitRemote.create(
|
||||||
|
{
|
||||||
|
"repo_id": cls.repo_cetmix_tower.id,
|
||||||
|
"source_id": cls.source.id,
|
||||||
|
"head_type": "branch",
|
||||||
|
"head": "main",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.repo_test = cls.Repo.create(
|
||||||
|
{
|
||||||
|
"name": "Test Repository",
|
||||||
|
"url": "https://github.com/cetmix-test/test.git",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_access(self):
|
||||||
|
"""Test that regular users have no access to git remotes"""
|
||||||
|
user_remote = self.GitRemote.with_user(self.user)
|
||||||
|
|
||||||
|
# Test CRUD operations
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_remote.create(
|
||||||
|
{
|
||||||
|
"repo_id": self.repo_test.id,
|
||||||
|
"url_protocol": "https",
|
||||||
|
"source_id": self.source.id,
|
||||||
|
"head": "main",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_remote.search([("id", "=", self.remote.id)])
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
self.remote.with_user(self.user).write({"head": "dev"})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
self.remote.with_user(self.user).unlink()
|
||||||
|
|
||||||
|
def test_manager_read_access(self):
|
||||||
|
"""Test manager read access rules"""
|
||||||
|
manager_remote = self.GitRemote.with_user(self.manager)
|
||||||
|
|
||||||
|
# Manager not in project user_ids or manager_ids - should not read
|
||||||
|
self.assertFalse(manager_remote.search([("id", "=", self.remote.id)]))
|
||||||
|
|
||||||
|
# Add manager to project user_ids - should read
|
||||||
|
self.project.write({"user_ids": [(4, self.manager.id)]})
|
||||||
|
remote = manager_remote.search([("id", "=", self.remote.id)])
|
||||||
|
self.assertTrue(remote)
|
||||||
|
self.assertEqual(remote.head, "main")
|
||||||
|
|
||||||
|
# Remove from user_ids, add to manager_ids - should read
|
||||||
|
self.project.write(
|
||||||
|
{"user_ids": [(3, self.manager.id)], "manager_ids": [(4, self.manager.id)]}
|
||||||
|
)
|
||||||
|
remote = manager_remote.search([("id", "=", self.remote.id)])
|
||||||
|
self.assertTrue(remote.exists())
|
||||||
|
|
||||||
|
def test_manager_write_access(self):
|
||||||
|
"""Test manager write/create access rules"""
|
||||||
|
manager_remote = self.GitRemote.with_user(self.manager)
|
||||||
|
|
||||||
|
# Create project as manager - should be added to manager_ids automatically
|
||||||
|
project = self.GitProject.with_user(self.manager).create(
|
||||||
|
{
|
||||||
|
"name": "Manager Project",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
source = self.GitSource.create(
|
||||||
|
{
|
||||||
|
"name": "Manager Source",
|
||||||
|
"git_project_id": project.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create remote in own project - should succeed
|
||||||
|
new_remote = manager_remote.create(
|
||||||
|
{
|
||||||
|
"repo_id": self.repo_test.id,
|
||||||
|
"url_protocol": "https",
|
||||||
|
"source_id": source.id,
|
||||||
|
"head_type": "branch",
|
||||||
|
"head": "main",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(new_remote.exists())
|
||||||
|
|
||||||
|
# Write to own remote - should succeed
|
||||||
|
new_remote.write({"head": "dev"})
|
||||||
|
self.assertEqual(new_remote.head, "dev")
|
||||||
|
|
||||||
|
# Write to other's remote - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
self.remote.with_user(self.manager).write({"head": "dev"})
|
||||||
|
|
||||||
|
def test_manager_unlink_access(self):
|
||||||
|
"""Test manager unlink access rules"""
|
||||||
|
# Create project and remote as manager_2
|
||||||
|
project = self.GitProject.with_user(self.manager_2).create(
|
||||||
|
{
|
||||||
|
"name": "Manager 2 Project",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
source = self.GitSource.create(
|
||||||
|
{
|
||||||
|
"name": "Manager 2 Source",
|
||||||
|
"git_project_id": project.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
remote = self.GitRemote.with_user(self.manager_2).create(
|
||||||
|
{
|
||||||
|
"repo_id": self.repo_test.id,
|
||||||
|
"url_protocol": "https",
|
||||||
|
"source_id": source.id,
|
||||||
|
"head_type": "branch",
|
||||||
|
"head": "main",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try delete as different manager - should fail even if added to manager_ids
|
||||||
|
project.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
remote.with_user(self.manager).unlink()
|
||||||
|
|
||||||
|
# Create remote as manager and try delete - should succeed
|
||||||
|
own_remote = self.GitRemote.with_user(self.manager).create(
|
||||||
|
{
|
||||||
|
"repo_id": self.repo_test.id,
|
||||||
|
"url_protocol": "https",
|
||||||
|
"source_id": source.id,
|
||||||
|
"head_type": "branch",
|
||||||
|
"head": "main",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(own_remote.exists())
|
||||||
|
own_remote.with_user(self.manager).unlink()
|
||||||
|
self.assertFalse(own_remote.exists())
|
||||||
|
|
||||||
|
def test_root_access(self):
|
||||||
|
"""Test root access rules"""
|
||||||
|
root_remote = self.GitRemote.with_user(self.root)
|
||||||
|
|
||||||
|
# Create
|
||||||
|
new_remote = root_remote.create(
|
||||||
|
{
|
||||||
|
"repo_id": self.repo_test.id,
|
||||||
|
"url_protocol": "https",
|
||||||
|
"source_id": self.source.id,
|
||||||
|
"head_type": "branch",
|
||||||
|
"head": "main",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(new_remote.exists())
|
||||||
|
|
||||||
|
# Read
|
||||||
|
remote = root_remote.search([("id", "=", self.remote.id)])
|
||||||
|
self.assertTrue(remote)
|
||||||
|
self.assertEqual(remote.head, "main")
|
||||||
|
|
||||||
|
# Write
|
||||||
|
self.remote.with_user(self.root).write({"head": "dev"})
|
||||||
|
self.assertEqual(self.remote.head, "dev")
|
||||||
|
|
||||||
|
# Delete
|
||||||
|
new_remote.with_user(self.root).unlink()
|
||||||
|
self.assertFalse(new_remote.exists())
|
||||||
|
|
||||||
|
def test_remote_provider_protocol_and_name(self):
|
||||||
|
"""Test if remote provider is detected correctly"""
|
||||||
|
|
||||||
|
# -- 1--
|
||||||
|
# GitHub + https
|
||||||
|
# Check if remote provider is detected correctly
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https.repo_provider,
|
||||||
|
"github",
|
||||||
|
"Provider is not detected correctly",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https.url_protocol,
|
||||||
|
"https",
|
||||||
|
"Protocol is not detected correctly",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https.name,
|
||||||
|
"remote_1",
|
||||||
|
"Name is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 2 --
|
||||||
|
# GitLab + ssh
|
||||||
|
# Check if remote provider is detected correctly
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_gitlab_ssh.repo_provider,
|
||||||
|
"gitlab",
|
||||||
|
"Provider is not detected correctly",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_gitlab_ssh.url_protocol,
|
||||||
|
"ssh",
|
||||||
|
"Protocol is not detected correctly",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_gitlab_ssh.name,
|
||||||
|
"remote_3",
|
||||||
|
"Name is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 3 --
|
||||||
|
# Bitbucket + https
|
||||||
|
# Check if remote provider is detected correctly
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_bitbucket_https.repo_provider,
|
||||||
|
"bitbucket",
|
||||||
|
"Provider is not detected correctly",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_bitbucket_https.url_protocol,
|
||||||
|
"https",
|
||||||
|
"Protocol is not detected correctly",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_bitbucket_https.name,
|
||||||
|
"remote_1",
|
||||||
|
"Name is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 4 --
|
||||||
|
# Other + ssh
|
||||||
|
# Check if remote provider is detected correctly
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_other_ssh.repo_provider,
|
||||||
|
"gitlab", # this is how giturlparse detects the provider
|
||||||
|
"Provider is not detected correctly",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_other_ssh.url_protocol,
|
||||||
|
"ssh",
|
||||||
|
"Protocol is not detected correctly",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_other_ssh.name,
|
||||||
|
"remote_2",
|
||||||
|
"Name is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_git_aggregator_prepare_url(self):
|
||||||
|
"""Test if url is prepared correctly"""
|
||||||
|
|
||||||
|
# -- 1 --
|
||||||
|
# GitHub + https
|
||||||
|
self.remote_github_https.repo_id.is_private = False
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https._git_aggregator_prepare_url(),
|
||||||
|
self.remote_github_https.repo_id.url,
|
||||||
|
"URL is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 2 --
|
||||||
|
# GitHub + https -> private
|
||||||
|
self.remote_github_https.repo_id.is_private = True
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https._git_aggregator_prepare_url(),
|
||||||
|
"https://$GITHUB_TOKEN:x-oauth-basic@github.com/cetmix-test/cetmix-tower-test.git",
|
||||||
|
"URL is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 3 --
|
||||||
|
# Gitlab + https
|
||||||
|
self.remote_gitlab_https.repo_id.is_private = False
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_gitlab_https._git_aggregator_prepare_url(),
|
||||||
|
self.remote_gitlab_https.repo_id.url,
|
||||||
|
"URL is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 4 --
|
||||||
|
# Gitlab + https -> private
|
||||||
|
self.remote_gitlab_https.repo_id.is_private = True
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_gitlab_https._git_aggregator_prepare_url(),
|
||||||
|
"https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git",
|
||||||
|
"URL is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 5 --
|
||||||
|
# Bitbucket + https
|
||||||
|
self.remote_bitbucket_https.repo_id.is_private = False
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_bitbucket_https._git_aggregator_prepare_url(),
|
||||||
|
self.remote_bitbucket_https.repo_id.url,
|
||||||
|
"URL is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 6 --
|
||||||
|
# Bitbucket + https -> private
|
||||||
|
self.remote_bitbucket_https.repo_id.is_private = True
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_bitbucket_https._git_aggregator_prepare_url(),
|
||||||
|
"https://x-token-auth:$BITBUCKET_TOKEN@bitbucket.com/cetmix-test/cetmix-tower-test-enterprise.git",
|
||||||
|
"URL is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 7 --
|
||||||
|
# Other + ssh
|
||||||
|
self.remote_other_ssh.repo_id.is_private = False
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_other_ssh._git_aggregator_prepare_url(),
|
||||||
|
self.remote_other_ssh.repo_id.url_ssh,
|
||||||
|
"URL is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_git_aggregator_prepare_head(self):
|
||||||
|
"""Test if head is prepared correctly"""
|
||||||
|
|
||||||
|
# -- 1 --
|
||||||
|
# GitHub + PR/MR as link
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https._git_aggregator_prepare_head(),
|
||||||
|
"refs/pull/123/head",
|
||||||
|
"Head is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 2 --
|
||||||
|
# GitHub + PR/MR as number
|
||||||
|
self.remote_github_https.write({"head": "123", "head_type": "pr"})
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https._git_aggregator_prepare_head(),
|
||||||
|
"refs/pull/123/head",
|
||||||
|
"Head is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 3 --
|
||||||
|
# GitHub + branch as name
|
||||||
|
self.remote_github_https.write({"head": "main", "head_type": "branch"})
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https._git_aggregator_prepare_head(),
|
||||||
|
self.remote_github_https.head,
|
||||||
|
"Head is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 4 --
|
||||||
|
# GitHub + branch as link
|
||||||
|
self.remote_github_https.write(
|
||||||
|
{
|
||||||
|
"head": "https://github.com/cetmix-test/cetmix-tower/tree/14.0-demo-branch",
|
||||||
|
"head_type": "branch",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https._git_aggregator_prepare_head(),
|
||||||
|
"14.0-demo-branch",
|
||||||
|
"Head is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 5 --
|
||||||
|
# GitHub + commit as number
|
||||||
|
self.remote_github_https.write({"head": "1234567890", "head_type": "commit"})
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https._git_aggregator_prepare_head(),
|
||||||
|
"1234567890",
|
||||||
|
"Head is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
# -- 6 --
|
||||||
|
# GitHub + commit as link
|
||||||
|
self.remote_github_https.head = (
|
||||||
|
"https://github.com/cetmix-test/cetmix-tower/commit/1234567890"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.remote_github_https._git_aggregator_prepare_head(),
|
||||||
|
"1234567890",
|
||||||
|
"Head is not prepared correctly",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_manager_server_based_access(self):
|
||||||
|
"""Test manager access to remotes through server relationships"""
|
||||||
|
manager_remote = self.GitRemote.with_user(self.manager)
|
||||||
|
|
||||||
|
# Create a server where manager is a user
|
||||||
|
server = self.Server.create(
|
||||||
|
{
|
||||||
|
"name": "Test Server",
|
||||||
|
"ip_v4_address": "localhost",
|
||||||
|
"ssh_username": "admin",
|
||||||
|
"ssh_password": "password",
|
||||||
|
"os_id": self.os_debian_10.id,
|
||||||
|
"user_ids": [(4, self.manager.id)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link project to server
|
||||||
|
file = self.File.create(
|
||||||
|
{
|
||||||
|
"name": "test_file",
|
||||||
|
"server_id": server.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.GitProjectRel.create(
|
||||||
|
{
|
||||||
|
"server_id": server.id,
|
||||||
|
"file_id": file.id,
|
||||||
|
"git_project_id": self.project.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Manager should be able to read remote through server relationship
|
||||||
|
remote = manager_remote.search([("id", "=", self.remote.id)])
|
||||||
|
self.assertTrue(remote)
|
||||||
|
self.assertEqual(remote.head, "main")
|
||||||
|
|
||||||
|
# Remove manager from server users
|
||||||
|
server.write({"user_ids": [(3, self.manager.id)]})
|
||||||
|
|
||||||
|
# Manager should not be able to read remote anymore
|
||||||
|
self.assertFalse(manager_remote.search([("id", "=", self.remote.id)]))
|
||||||
|
|
||||||
|
# Add manager to server managers
|
||||||
|
server.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
|
||||||
|
# Manager should be able to read remote again
|
||||||
|
remote = manager_remote.search([("id", "=", self.remote.id)])
|
||||||
|
self.assertTrue(remote)
|
||||||
|
self.assertEqual(remote.head, "main")
|
||||||
84
addons/cetmix_tower_git/tests/test_repo.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
from .common import CommonTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestRepo(CommonTest):
|
||||||
|
"""Test class for git repository."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
def test_repo_create_from_url_https_success(self):
|
||||||
|
"""Test if repository is created correctly"""
|
||||||
|
# -- 1 --
|
||||||
|
# Valid HTTPS URL
|
||||||
|
repo = self.Repo.create(
|
||||||
|
{
|
||||||
|
"url": "https://github.com/memes-demo/doge-memes.git",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
repo.invalidate_recordset()
|
||||||
|
|
||||||
|
self.assertEqual(repo.name, "github.com/memes-demo/doge-memes")
|
||||||
|
self.assertEqual(repo.host, "github.com")
|
||||||
|
self.assertEqual(repo.owner_id.name, "memes-demo")
|
||||||
|
self.assertEqual(repo.provider, "github")
|
||||||
|
self.assertEqual(repo.is_private, False)
|
||||||
|
self.assertEqual(repo.url_ssh, "git@github.com:memes-demo/doge-memes.git")
|
||||||
|
self.assertEqual(repo.url_git, "git://github.com/memes-demo/doge-memes.git")
|
||||||
|
|
||||||
|
def test_repo_create_from_url_ssh_success(self):
|
||||||
|
"""Test if repository is created correctly"""
|
||||||
|
# -- 1 --
|
||||||
|
# Valid SSH URL
|
||||||
|
repo = self.Repo.create(
|
||||||
|
{
|
||||||
|
"url": "git@gitlab.com:chad-guy/chad-guy.git",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
repo.invalidate_recordset()
|
||||||
|
|
||||||
|
self.assertEqual(repo.name, "gitlab.com/chad-guy/chad-guy")
|
||||||
|
self.assertEqual(repo.host, "gitlab.com")
|
||||||
|
self.assertEqual(repo.owner_id.name, "chad-guy")
|
||||||
|
self.assertEqual(repo.provider, "gitlab")
|
||||||
|
self.assertEqual(repo.is_private, False)
|
||||||
|
self.assertEqual(repo.url, "https://gitlab.com/chad-guy/chad-guy.git")
|
||||||
|
self.assertEqual(repo.url_git, "git://gitlab.com/chad-guy/chad-guy.git")
|
||||||
|
|
||||||
|
def test_repo_create_from_url_git_success(self):
|
||||||
|
"""Test if repository is created correctly"""
|
||||||
|
# -- 1 --
|
||||||
|
# Valid GIT URL
|
||||||
|
repo = self.Repo.create(
|
||||||
|
{
|
||||||
|
"url": "git://bitbucket.com/much-pepe/pepe-memes.git",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(repo.name, "bitbucket.com/much-pepe/pepe-memes")
|
||||||
|
self.assertEqual(repo.host, "bitbucket.com")
|
||||||
|
self.assertEqual(repo.owner_id.name, "much-pepe")
|
||||||
|
self.assertEqual(repo.provider, "bitbucket")
|
||||||
|
self.assertEqual(repo.is_private, False)
|
||||||
|
self.assertEqual(repo.url_ssh, "git@bitbucket.com:much-pepe/pepe-memes.git")
|
||||||
|
self.assertEqual(repo.url, "https://bitbucket.com/much-pepe/pepe-memes.git")
|
||||||
|
|
||||||
|
def test_repo_create_from_url_fails(self):
|
||||||
|
"""Test if repository creation fails with invalid URLs"""
|
||||||
|
|
||||||
|
# Invalid URL 1
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
self.Repo.create(
|
||||||
|
{
|
||||||
|
"url": "something.com",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Invalid URL 2
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
self.Repo.create(
|
||||||
|
{
|
||||||
|
"url": "random string",
|
||||||
|
}
|
||||||
|
)
|
||||||
198
addons/cetmix_tower_git/tests/test_server.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
try:
|
||||||
|
from odoo.addons.queue_job.tests.common import trap_jobs
|
||||||
|
except ImportError:
|
||||||
|
trap_jobs = None
|
||||||
|
|
||||||
|
from .common import CommonTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestServer(CommonTest):
|
||||||
|
"""Test setting git project to server from plan line."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
cls.GitProjectRel.create(
|
||||||
|
{
|
||||||
|
"git_project_id": cls.git_project_1.id,
|
||||||
|
"server_id": cls.server_test_1.id,
|
||||||
|
"file_id": cls.server_1_file_1.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_server_creation_running_flight_plan(self):
|
||||||
|
"""Test that server is created with git project from plan line."""
|
||||||
|
git_project = self.GitProject.create(
|
||||||
|
{
|
||||||
|
"name": "Test Git Project",
|
||||||
|
"manager_ids": [(4, self.manager.id)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
file_template = self.FileTemplate.create(
|
||||||
|
{
|
||||||
|
"name": "Git Config Template",
|
||||||
|
"file_name": "repos.yaml",
|
||||||
|
"server_dir": "/var/test",
|
||||||
|
"code": "repositories:\n test_repo:\n "
|
||||||
|
"url: https://github.com/test/repo.git\n target: main",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
command = self.Command.create(
|
||||||
|
{
|
||||||
|
"name": "Create Git Config File",
|
||||||
|
"action": "file_using_template",
|
||||||
|
"file_template_id": file_template.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
flight_plan = self.Plan.create(
|
||||||
|
{
|
||||||
|
"name": "Git Project Setup Plan",
|
||||||
|
"note": "Sets up a git project on the server",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.plan_line.create(
|
||||||
|
{
|
||||||
|
"plan_id": flight_plan.id,
|
||||||
|
"command_id": command.id,
|
||||||
|
"sequence": 10,
|
||||||
|
"git_project_id": git_project.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
server_template = self.ServerTemplate.create(
|
||||||
|
{
|
||||||
|
"name": "Git Server Template",
|
||||||
|
"ssh_port": 22,
|
||||||
|
"ssh_username": "admin",
|
||||||
|
"ssh_password": "password",
|
||||||
|
"ssh_auth_mode": "p",
|
||||||
|
"os_id": self.os_debian_10.id,
|
||||||
|
"flight_plan_id": flight_plan.id,
|
||||||
|
"manager_ids": [(4, self.manager.id)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
action = server_template.action_create_server()
|
||||||
|
|
||||||
|
# Open the wizard and fill in the data
|
||||||
|
wizard = (
|
||||||
|
self.env["cx.tower.server.template.create.wizard"]
|
||||||
|
.with_context(**action["context"])
|
||||||
|
.create(
|
||||||
|
{
|
||||||
|
"name": "Git Server",
|
||||||
|
"ip_v4_address": "192.168.1.10",
|
||||||
|
"server_template_id": server_template.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# If cetmix_tower_server_queue module is installed, test async processing
|
||||||
|
if self.env["ir.module.module"].search_count(
|
||||||
|
[("name", "=", "cetmix_tower_server_queue"), ("state", "=", "installed")]
|
||||||
|
):
|
||||||
|
with trap_jobs() as trap:
|
||||||
|
wizard.action_confirm()
|
||||||
|
|
||||||
|
# Verify that jobs were created
|
||||||
|
self.assertGreater(
|
||||||
|
len(trap.enqueued_jobs), 0, "Jobs should have been enqueued"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Execute all trapped jobs to simulate async processing
|
||||||
|
trap.perform_enqueued_jobs()
|
||||||
|
else:
|
||||||
|
wizard.action_confirm()
|
||||||
|
|
||||||
|
# Now search for the created records after jobs have been executed
|
||||||
|
server = self.Server.search(
|
||||||
|
[
|
||||||
|
("name", "=", "Git Server"),
|
||||||
|
("server_template_id", "=", server_template.id),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.assertEqual(len(server), 1, "Exactly one server should have been created")
|
||||||
|
|
||||||
|
# Verify the file was created
|
||||||
|
file = self.File.search(
|
||||||
|
[("server_id", "=", server.id), ("name", "=", "repos.yaml")]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
len(file), 1, "Exactly one git config file should have been created"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify the git project relation exists
|
||||||
|
git_project_rel = self.GitProjectRel.search(
|
||||||
|
[
|
||||||
|
("server_id", "=", server.id),
|
||||||
|
("git_project_id", "=", git_project.id),
|
||||||
|
("file_id", "=", file.id),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
len(git_project_rel), 1, "Exactly one git project relation should exist"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
git_project_rel.file_id,
|
||||||
|
file,
|
||||||
|
"The related file should be the git config file",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
git_project_rel.git_project_id,
|
||||||
|
git_project,
|
||||||
|
"The related git project should match the one in the flight plan",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
git_project_rel.project_format,
|
||||||
|
git_project._default_project_format(),
|
||||||
|
"Project format should match the default format",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_server_get_servers_by_git_ref_success(self):
|
||||||
|
"""Check the success case of server.get_servers_by_git_ref"""
|
||||||
|
|
||||||
|
# 1. URL only
|
||||||
|
servers = self.Server.get_servers_by_git_ref(
|
||||||
|
self.remote_github_https.repo_id.url
|
||||||
|
)
|
||||||
|
self.assertEqual(servers, self.server_test_1)
|
||||||
|
|
||||||
|
# 2. Specific URL with specific head
|
||||||
|
servers = self.Server.get_servers_by_git_ref(
|
||||||
|
self.remote_github_https.repo_id.url, "123"
|
||||||
|
)
|
||||||
|
self.assertEqual(servers, self.server_test_1)
|
||||||
|
|
||||||
|
# 2. Specific URL with specific head and head type
|
||||||
|
servers = self.Server.get_servers_by_git_ref(
|
||||||
|
self.remote_github_https.repo_id.url, "123", "pr"
|
||||||
|
)
|
||||||
|
self.assertEqual(servers, self.server_test_1)
|
||||||
|
|
||||||
|
def test_server_get_servers_by_git_ref_no_match(self):
|
||||||
|
"""Check the no match case of server.get_servers_by_git_ref"""
|
||||||
|
|
||||||
|
# 1. Repo link does not exist
|
||||||
|
servers = self.Server.get_servers_by_git_ref(
|
||||||
|
"https://github.com/other-org/other-repo.git", "main", "branch"
|
||||||
|
)
|
||||||
|
self.assertFalse(servers)
|
||||||
|
|
||||||
|
# 2. Repo link exists, but remote does not exist
|
||||||
|
servers = self.Server.get_servers_by_git_ref(
|
||||||
|
self.repo_cetmix_tower.url, "3311", "pr"
|
||||||
|
)
|
||||||
|
self.assertFalse(servers)
|
||||||
|
|
||||||
|
# 3. Repo link exists, but remote type does not exist
|
||||||
|
servers = self.Server.get_servers_by_git_ref(
|
||||||
|
self.repo_cetmix_tower.url, "main", "commit"
|
||||||
|
)
|
||||||
|
self.assertFalse(servers)
|
||||||
226
addons/cetmix_tower_git/tests/test_source.py
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
from odoo.exceptions import AccessError
|
||||||
|
|
||||||
|
from .common import CommonTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestSource(CommonTest):
|
||||||
|
"""Test class for git source."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
# Create another manager for testing
|
||||||
|
cls.manager_2 = cls.Users.create(
|
||||||
|
{
|
||||||
|
"name": "Second Manager",
|
||||||
|
"login": "manager2",
|
||||||
|
"email": "manager2@test.com",
|
||||||
|
"groups_id": [(4, cls.env.ref("cetmix_tower_server.group_manager").id)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test project and source as root
|
||||||
|
cls.project = cls.GitProject.create(
|
||||||
|
{
|
||||||
|
"name": "Test Project",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.source = cls.GitSource.create(
|
||||||
|
{
|
||||||
|
"name": "Test Source",
|
||||||
|
"git_project_id": cls.project.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_user_access(self):
|
||||||
|
"""Test that regular users have no access to git sources"""
|
||||||
|
user_source = self.GitSource.with_user(self.user)
|
||||||
|
|
||||||
|
# Test CRUD operations
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_source.create(
|
||||||
|
{
|
||||||
|
"name": "New Source",
|
||||||
|
"git_project_id": self.project.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_source.browse(self.source.id).read(["name"])
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_source.browse(self.source.id).write({"name": "Updated Name"})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
user_source.browse(self.source.id).unlink()
|
||||||
|
|
||||||
|
def test_manager_read_access(self):
|
||||||
|
"""Test manager read access rules"""
|
||||||
|
manager_source = self.GitSource.with_user(self.manager)
|
||||||
|
|
||||||
|
# Manager not in project user_ids or manager_ids - should not read
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_source.browse(self.source.id).read(["name"])
|
||||||
|
|
||||||
|
# Add manager to project user_ids - should read
|
||||||
|
self.project.write({"user_ids": [(4, self.manager.id)]})
|
||||||
|
self.assertEqual(manager_source.browse(self.source.id).name, "Test Source")
|
||||||
|
|
||||||
|
# Remove from user_ids, add to manager_ids - should read
|
||||||
|
self.project.write(
|
||||||
|
{"user_ids": [(3, self.manager.id)], "manager_ids": [(4, self.manager.id)]}
|
||||||
|
)
|
||||||
|
self.assertEqual(manager_source.browse(self.source.id).name, "Test Source")
|
||||||
|
|
||||||
|
def test_manager_write_access(self):
|
||||||
|
"""Test manager write/create access rules"""
|
||||||
|
manager_source = self.GitSource.with_user(self.manager)
|
||||||
|
|
||||||
|
# Create project as manager - should be added to manager_ids automatically
|
||||||
|
project = self.GitProject.with_user(self.manager).create(
|
||||||
|
{
|
||||||
|
"name": "Manager Project",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertIn(self.manager, project.manager_ids)
|
||||||
|
|
||||||
|
# Create source in own project - should succeed
|
||||||
|
new_source = manager_source.create(
|
||||||
|
{
|
||||||
|
"name": "New Source",
|
||||||
|
"git_project_id": project.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(new_source.exists())
|
||||||
|
|
||||||
|
# Write to own source - should succeed
|
||||||
|
new_source.write({"name": "Updated Name"})
|
||||||
|
self.assertEqual(new_source.name, "Updated Name")
|
||||||
|
|
||||||
|
# Write to other's source - should fail
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_source.browse(self.source.id).write({"name": "Updated Name"})
|
||||||
|
|
||||||
|
def test_manager_unlink_access(self):
|
||||||
|
"""Test manager unlink access rules"""
|
||||||
|
# Create project and source as manager_2
|
||||||
|
project = self.GitProject.with_user(self.manager_2).create(
|
||||||
|
{
|
||||||
|
"name": "Manager 2 Project",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
source = self.GitSource.with_user(self.manager_2).create(
|
||||||
|
{
|
||||||
|
"name": "Source to Delete",
|
||||||
|
"git_project_id": project.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
manager_source = self.GitSource.with_user(self.manager)
|
||||||
|
|
||||||
|
# Try delete as different manager - should fail even if added to manager_ids
|
||||||
|
project.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_source.browse(source.id).unlink()
|
||||||
|
|
||||||
|
# Create source as manager and try delete - should succeed
|
||||||
|
own_source = manager_source.create(
|
||||||
|
{
|
||||||
|
"name": "Own Source",
|
||||||
|
"git_project_id": project.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(own_source.exists())
|
||||||
|
own_source.unlink()
|
||||||
|
self.assertFalse(own_source.exists())
|
||||||
|
|
||||||
|
def test_root_access(self):
|
||||||
|
"""Test root access rules"""
|
||||||
|
root_source = self.GitSource.with_user(self.root)
|
||||||
|
|
||||||
|
# Create
|
||||||
|
new_source = root_source.create(
|
||||||
|
{
|
||||||
|
"name": "Root Source",
|
||||||
|
"git_project_id": self.project.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertTrue(new_source.exists())
|
||||||
|
|
||||||
|
# Read
|
||||||
|
self.assertEqual(root_source.browse(self.source.id).name, "Test Source")
|
||||||
|
|
||||||
|
# Write
|
||||||
|
root_source.browse(self.source.id).write({"name": "Updated by Root"})
|
||||||
|
self.assertEqual(self.source.name, "Updated by Root")
|
||||||
|
|
||||||
|
# Delete
|
||||||
|
new_source.unlink()
|
||||||
|
self.assertFalse(new_source.exists())
|
||||||
|
|
||||||
|
def test_source_git_aggregator_prepare_record(self):
|
||||||
|
"""Test if source prepare record method works correctly."""
|
||||||
|
|
||||||
|
# -- 1 --
|
||||||
|
# Source 1
|
||||||
|
expected_result = {
|
||||||
|
"remotes": {
|
||||||
|
"remote_1": "https://github.com/cetmix-test/cetmix-tower-test.git",
|
||||||
|
"remote_2": "https://$GITLAB_TOKEN_NAME:$GITLAB_TOKEN@my.gitlab.com/cetmix-test/cetmix-tower-test.git",
|
||||||
|
"remote_3": "git@my.gitlab.com:cetmix-test/cetmix-tower-test.git",
|
||||||
|
},
|
||||||
|
"merges": [
|
||||||
|
{"remote": "remote_1", "ref": "refs/pull/123/head"},
|
||||||
|
{"remote": "remote_2", "ref": "main"},
|
||||||
|
{"remote": "remote_3", "ref": "10000000"},
|
||||||
|
],
|
||||||
|
"target": "remote_1",
|
||||||
|
}
|
||||||
|
prepared_result = self.git_source_1._git_aggregator_prepare_record()
|
||||||
|
self.assertEqual(
|
||||||
|
prepared_result, expected_result, "Prepared result is not correct"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_manager_server_based_access(self):
|
||||||
|
"""Test manager access to sources through server relationships"""
|
||||||
|
manager_source = self.GitSource.with_user(self.manager)
|
||||||
|
|
||||||
|
# Create a server where manager is a user
|
||||||
|
server = self.Server.create(
|
||||||
|
{
|
||||||
|
"name": "Test Server",
|
||||||
|
"ip_v4_address": "localhost",
|
||||||
|
"ssh_username": "admin",
|
||||||
|
"ssh_password": "password",
|
||||||
|
"os_id": self.os_debian_10.id,
|
||||||
|
"user_ids": [(4, self.manager.id)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link project to server
|
||||||
|
file = self.File.create(
|
||||||
|
{
|
||||||
|
"name": "test_file",
|
||||||
|
"server_id": server.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.GitProjectRel.create(
|
||||||
|
{
|
||||||
|
"server_id": server.id,
|
||||||
|
"file_id": file.id,
|
||||||
|
"git_project_id": self.project.id,
|
||||||
|
"project_format": "git_aggregator",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Manager should be able to read source through server relationship
|
||||||
|
self.assertEqual(manager_source.browse(self.source.id).name, "Test Source")
|
||||||
|
|
||||||
|
# Remove manager from server users
|
||||||
|
server.write({"user_ids": [(3, self.manager.id)]})
|
||||||
|
|
||||||
|
# Manager should not be able to read source anymore
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
manager_source.browse(self.source.id).read(["name"])
|
||||||
|
|
||||||
|
# Add manager to server managers
|
||||||
|
server.write({"manager_ids": [(4, self.manager.id)]})
|
||||||
|
|
||||||
|
# Manager should be able to read source again
|
||||||
|
self.assertEqual(manager_source.browse(self.source.id).name, "Test Source")
|
||||||
0
addons/cetmix_tower_git/tools/git_aggregator.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="cx_tower_file_template_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.file.template.view.form</field>
|
||||||
|
<field name="model">cx.tower.file.template</field>
|
||||||
|
<field
|
||||||
|
name="inherit_id"
|
||||||
|
ref="cetmix_tower_server.cx_tower_file_template_view_form"
|
||||||
|
/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="source" position="after">
|
||||||
|
<field
|
||||||
|
name="git_project_id"
|
||||||
|
attrs="{'invisible': [('git_project_id', '=', False)]}"
|
||||||
|
/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
18
addons/cetmix_tower_git/views/cx_tower_file_views.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="cx_tower_file_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.file.view.form</field>
|
||||||
|
<field name="model">cx.tower.file</field>
|
||||||
|
<field name="inherit_id" ref="cetmix_tower_server.cx_tower_file_view_form" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="auto_sync" position="before">
|
||||||
|
<field
|
||||||
|
name="git_project_id"
|
||||||
|
attrs="{'invisible': [('git_project_id', '=', False)]}"
|
||||||
|
/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
236
addons/cetmix_tower_git/views/cx_tower_git_project_views.xml
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Tree View -->
|
||||||
|
<record id="cx_tower_git_project_view_tree" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.project.tree</field>
|
||||||
|
<field name="model">cx.tower.git.project</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree>
|
||||||
|
<field name="name" />
|
||||||
|
<field name="server_ids" widget="many2many_tags" />
|
||||||
|
<field name="active" widget="boolean_toggle" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Form View -->
|
||||||
|
<record id="cx_tower_git_project_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.project.form</field>
|
||||||
|
<field name="model">cx.tower.git.project</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<widget
|
||||||
|
name="web_ribbon"
|
||||||
|
title="Archived"
|
||||||
|
bg_color="bg-danger"
|
||||||
|
attrs="{'invisible': [('active', '=', True)]}"
|
||||||
|
/>
|
||||||
|
<div class="oe_title">
|
||||||
|
<h1>
|
||||||
|
<field name="name" placeholder="Name" />
|
||||||
|
</h1>
|
||||||
|
<h3>
|
||||||
|
<field
|
||||||
|
name="reference"
|
||||||
|
placeholder="Reference. Can contain English letters, digits and '_'. Leave blank to autogenerate"
|
||||||
|
/>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group string="General">
|
||||||
|
<field name="active" invisible="1" />
|
||||||
|
<field
|
||||||
|
name="server_ids"
|
||||||
|
widget="many2many_tags"
|
||||||
|
attrs="{'invisible': [('server_ids', '=', [])]}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="file_template_ids"
|
||||||
|
widget="many2many_tags"
|
||||||
|
attrs="{'invisible': [('file_template_ids', '=', [])]}"
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
<group string="Git Aggregator">
|
||||||
|
<field
|
||||||
|
name="git_aggregator_root_dir"
|
||||||
|
string="Root Directory"
|
||||||
|
placeholder="Git aggregator root directory where sources will be cloned. Leave blank to use '.'"
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
<field name="note" placeholder="Put your notes here..." />
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page name="sources" string="Sources">
|
||||||
|
<field name="source_ids">
|
||||||
|
<tree
|
||||||
|
decoration-muted="not enabled"
|
||||||
|
decoration-info="remote_count_private == remote_count and remote_count > 0"
|
||||||
|
decoration-warning="remote_count_private != remote_count and remote_count > 0 and remote_count_private > 0"
|
||||||
|
>
|
||||||
|
<field name="sequence" widget="handle" />
|
||||||
|
<field
|
||||||
|
name="name"
|
||||||
|
placeholder="..to be autogenerated"
|
||||||
|
/>
|
||||||
|
<field name="remote_count" optional="show" />
|
||||||
|
<field
|
||||||
|
name="remote_count_private"
|
||||||
|
optional="hide"
|
||||||
|
/>
|
||||||
|
<field name="enabled" widget="boolean_toggle" />
|
||||||
|
<field name="reference" optional="hide" />
|
||||||
|
<field name="create_uid" optional="hide" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
<field name="has_private_remotes" invisible="1" />
|
||||||
|
<field name="has_partially_private_remotes" invisible="1" />
|
||||||
|
<div
|
||||||
|
class="text-info"
|
||||||
|
attrs="{'invisible': [('has_private_remotes', '=', False)]}"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
* Sources where all remotes are private
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-warning"
|
||||||
|
attrs="{'invisible': [('has_partially_private_remotes', '=', False)]}"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
* Sources where some remotes are private
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</page>
|
||||||
|
<page name="files" string="Files">
|
||||||
|
<field name="git_project_rel_ids">
|
||||||
|
<tree editable="bottom">
|
||||||
|
<field
|
||||||
|
name="server_id"
|
||||||
|
options="{'no_create': True, 'no_create_edit': True}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="file_id"
|
||||||
|
options="{'no_create': True, 'no_create_edit': True}"
|
||||||
|
/>
|
||||||
|
<field name="project_format" />
|
||||||
|
<field name="auto_sync" />
|
||||||
|
<button
|
||||||
|
type="object"
|
||||||
|
name="action_open_server"
|
||||||
|
string="Open Server"
|
||||||
|
title="Open Server"
|
||||||
|
class="btn-secondary"
|
||||||
|
/>
|
||||||
|
</tree>
|
||||||
|
<form>
|
||||||
|
<group>
|
||||||
|
<field name="server_id" />
|
||||||
|
<field name="file_id" />
|
||||||
|
<field name="project_format" />
|
||||||
|
<field name="auto_sync" />
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page name="file_templates" string="File Templates">
|
||||||
|
<field name="git_project_file_template_rel_ids">
|
||||||
|
<tree editable="bottom">
|
||||||
|
<field
|
||||||
|
name="file_template_id"
|
||||||
|
options="{'no_create': True, 'no_create_edit': True}"
|
||||||
|
/>
|
||||||
|
<field name="project_format" />
|
||||||
|
<button
|
||||||
|
type="object"
|
||||||
|
string="Open file template"
|
||||||
|
name="action_open_file_template"
|
||||||
|
title="Open File Template"
|
||||||
|
class="btn-secondary"
|
||||||
|
/>
|
||||||
|
</tree>
|
||||||
|
<form>
|
||||||
|
<group>
|
||||||
|
<field name="file_template_id" />
|
||||||
|
<field name="project_format" />
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page
|
||||||
|
name="repos"
|
||||||
|
string="Repos"
|
||||||
|
groups="base.group_no_one"
|
||||||
|
attrs="{'invisible': [('repo_ids', '=', [])]}"
|
||||||
|
>
|
||||||
|
<field name="repo_ids" />
|
||||||
|
</page>
|
||||||
|
<page
|
||||||
|
name="access"
|
||||||
|
string="Access"
|
||||||
|
groups="cetmix_tower_server.group_manager"
|
||||||
|
>
|
||||||
|
<group name="access">
|
||||||
|
<field
|
||||||
|
name="user_ids"
|
||||||
|
widget="many2many_tags"
|
||||||
|
placeholder="users who can view this record"
|
||||||
|
options="{'no_create': True}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="manager_ids"
|
||||||
|
widget="many2many_tags"
|
||||||
|
placeholder="managers who can modify this record"
|
||||||
|
options="{'no_create': True}"
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
<div
|
||||||
|
class="alert alert-warning"
|
||||||
|
role="alert"
|
||||||
|
style="margin-bottom:0px;"
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b
|
||||||
|
>Users.</b> All users who have "Manager" group and are either set in "Users" or in "Managers" in <b
|
||||||
|
><u>all</u></b> related servers.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b
|
||||||
|
>Managers.</b> All users who have "Manager" group and are set as "Managers" in <b
|
||||||
|
><u>all</u></b> related servers.
|
||||||
|
This is done to avoid unpredictable consequences when some of the servers are not updated due to access restrictions when a project is updated.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
You can edit these fields at your own risk. However keep in mind that they will be automatically updated each time related servers are added, removed or updated.
|
||||||
|
</div>
|
||||||
|
</page>
|
||||||
|
<page name="yaml" string="YAML">
|
||||||
|
<div groups="!cetmix_tower_yaml.group_export">
|
||||||
|
<h3
|
||||||
|
>You must be a member of the "YAML/Export" group to export data as YAML.</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="object"
|
||||||
|
groups="cetmix_tower_yaml.group_export"
|
||||||
|
class="oe_highlight"
|
||||||
|
name="action_open_yaml_export_wizard"
|
||||||
|
string="Export YAML"
|
||||||
|
/>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
|
<record id="cx_tower_git_project_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Git Projects</field>
|
||||||
|
<field name="res_model">cx.tower.git.project</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
84
addons/cetmix_tower_git/views/cx_tower_git_remote_views.xml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Tree View -->
|
||||||
|
<record id="cx_tower_git_remote_view_tree" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.remote.tree</field>
|
||||||
|
<field name="model">cx.tower.git.remote</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree decoration-info="is_private == True">
|
||||||
|
<field
|
||||||
|
name="repo_id"
|
||||||
|
placeholder="select or enter a link"
|
||||||
|
options="{'no_create_edit': True}"
|
||||||
|
/>
|
||||||
|
<field name="is_private" optional="hide" />
|
||||||
|
<field name="head_type" />
|
||||||
|
<field name="head" />
|
||||||
|
<field name="enabled" widget="boolean_toggle" />
|
||||||
|
<field name="create_uid" optional="hide" />
|
||||||
|
<field name="reference" optional="hide" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Form View -->
|
||||||
|
<record id="cx_tower_git_remote_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.remote.form</field>
|
||||||
|
<field name="model">cx.tower.git.remote</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<widget
|
||||||
|
name="web_ribbon"
|
||||||
|
title="Disabled"
|
||||||
|
bg_color="bg-danger"
|
||||||
|
attrs="{'invisible': [('enabled', '=', True)]}"
|
||||||
|
/>
|
||||||
|
<group>
|
||||||
|
<field name="sequence" />
|
||||||
|
<field name="enabled" />
|
||||||
|
<field name="active" invisible="1" />
|
||||||
|
<field
|
||||||
|
name="repo_id"
|
||||||
|
placeholder="select or enter a link"
|
||||||
|
options="{'no_create_edit': True}"
|
||||||
|
/>
|
||||||
|
<field name="is_private" />
|
||||||
|
<field
|
||||||
|
name="url_protocol"
|
||||||
|
widget="radio"
|
||||||
|
options="{'horizontal': true}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="head_type"
|
||||||
|
widget="radio"
|
||||||
|
options="{'horizontal': true}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="head"
|
||||||
|
placeholder="Branch/PR/commit number or link"
|
||||||
|
/>
|
||||||
|
<field name="create_uid" />
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
|
<record id="action_cx_tower_git_remote" model="ir.actions.act_window">
|
||||||
|
<field name="name">Git Remotes</field>
|
||||||
|
<field name="res_model">cx.tower.git.remote</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
Create your first git remote!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Git remotes represent branches, pull requests, or commits from git repositories.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Tree View -->
|
||||||
|
<record id="cx_tower_git_repo_owner_view_tree" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.repo.owner.tree</field>
|
||||||
|
<field name="model">cx.tower.git.repo.owner</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree>
|
||||||
|
<field name="display_name" />
|
||||||
|
<field name="name" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Form View -->
|
||||||
|
<record id="cx_tower_git_repo_owner_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.repo.owner.form</field>
|
||||||
|
<field name="model">cx.tower.git.repo.owner</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="display_name" placeholder="e.g., Cetmix, OCA" />
|
||||||
|
<field name="name" placeholder="e.g., cetmix, oca" />
|
||||||
|
<field
|
||||||
|
name="reference"
|
||||||
|
placeholder="Can contain English letters, digits and '_'. Leave blank to autogenerate"
|
||||||
|
/>
|
||||||
|
<field name="secret_id" />
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Repositories" name="repositories">
|
||||||
|
<field name="repo_ids" readonly="1">
|
||||||
|
<tree>
|
||||||
|
<field name="name" />
|
||||||
|
<field name="reference" optional="hide" />
|
||||||
|
<field name="provider" />
|
||||||
|
<field name="is_private" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
|
<record id="action_cx_tower_git_repo_owner" model="ir.actions.act_window">
|
||||||
|
<field name="name">Repository Owners</field>
|
||||||
|
<field name="res_model">cx.tower.git.repo.owner</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
Create your first repository owner!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Repository owners represent organizations or users that own git repositories.
|
||||||
|
Examples include "cetmix", "OCA", etc.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
163
addons/cetmix_tower_git/views/cx_tower_git_repo_views.xml
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Tree View -->
|
||||||
|
<record id="cx_tower_git_repo_view_tree" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.repo.tree</field>
|
||||||
|
<field name="model">cx.tower.git.repo</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree decoration-info="is_private == True">
|
||||||
|
<field name="name" />
|
||||||
|
<field name="reference" optional="hide" />
|
||||||
|
<field name="provider" optional="show" />
|
||||||
|
<field name="owner_id" optional="hide" />
|
||||||
|
<field name="is_private" optional="hide" />
|
||||||
|
<field name="remote_count" optional="hide" />
|
||||||
|
<field name="git_project_count" optional="hide" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Form View -->
|
||||||
|
<record id="cx_tower_git_repo_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.repo.form</field>
|
||||||
|
<field name="model">cx.tower.git.repo</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button
|
||||||
|
name="action_view_remotes"
|
||||||
|
type="object"
|
||||||
|
string="Remotes"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-external-link"
|
||||||
|
attrs="{'invisible': [('remote_count', '=', 0)]}"
|
||||||
|
>
|
||||||
|
<field
|
||||||
|
name="remote_count"
|
||||||
|
widget="statinfo"
|
||||||
|
string="Remotes"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
name="action_view_projects"
|
||||||
|
type="object"
|
||||||
|
string="GitProjects"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-folder"
|
||||||
|
attrs="{'invisible': [('git_project_count', '=', 0)]}"
|
||||||
|
>
|
||||||
|
<field
|
||||||
|
name="git_project_count"
|
||||||
|
widget="statinfo"
|
||||||
|
string="Projects"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group name="info">
|
||||||
|
<field
|
||||||
|
name="url"
|
||||||
|
placeholder="https, ssh or git formats are accepted"
|
||||||
|
/>
|
||||||
|
<field name="url_ssh" />
|
||||||
|
<field name="url_git" />
|
||||||
|
<field name="repo" placeholder="e.g., cetmix-tower, odoo" />
|
||||||
|
<field name="host" />
|
||||||
|
</group>
|
||||||
|
<group name="details">
|
||||||
|
<field
|
||||||
|
name="reference"
|
||||||
|
placeholder="Can contain English letters, digits and '_'. Leave blank to autogenerate"
|
||||||
|
/>
|
||||||
|
<field name="active" />
|
||||||
|
<field name="owner_id" />
|
||||||
|
<field name="provider" />
|
||||||
|
<field name="is_private" />
|
||||||
|
<field name="secret_id" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Search View -->
|
||||||
|
<record id="cx_tower_git_repo_view_search" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.repo.search</field>
|
||||||
|
<field name="model">cx.tower.git.repo</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search>
|
||||||
|
<field
|
||||||
|
name="repo"
|
||||||
|
string="Name/Reference"
|
||||||
|
filter_domain="['|', ('repo', 'ilike', self), ('reference', 'ilike', self)]"
|
||||||
|
/>
|
||||||
|
<field name="owner_id" string="Org" />
|
||||||
|
<field name="provider" />
|
||||||
|
|
||||||
|
<filter
|
||||||
|
string="Public"
|
||||||
|
name="public"
|
||||||
|
domain="[('is_private', '=', False)]"
|
||||||
|
/>
|
||||||
|
<filter
|
||||||
|
string="Private"
|
||||||
|
name="private"
|
||||||
|
domain="[('is_private', '=', True)]"
|
||||||
|
/>
|
||||||
|
<separator />
|
||||||
|
<filter
|
||||||
|
string="Provider: Other"
|
||||||
|
name="no_provider"
|
||||||
|
domain="[('provider', '=', 'other')]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<group expand="0" string="Group By">
|
||||||
|
<filter
|
||||||
|
string="Provider"
|
||||||
|
name="group_provider"
|
||||||
|
context="{'group_by': 'provider'}"
|
||||||
|
/>
|
||||||
|
<filter
|
||||||
|
string="Org"
|
||||||
|
name="group_owner"
|
||||||
|
context="{'group_by': 'owner_id'}"
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
<searchpanel>
|
||||||
|
<field
|
||||||
|
name="provider"
|
||||||
|
string="Provider"
|
||||||
|
icon="fa-globe"
|
||||||
|
enable_counters="1"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="owner_id"
|
||||||
|
string="Org"
|
||||||
|
icon="fa-building"
|
||||||
|
enable_counters="1"
|
||||||
|
/>
|
||||||
|
</searchpanel>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
|
<record id="action_cx_tower_git_repo" model="ir.actions.act_window">
|
||||||
|
<field name="name">Repositories</field>
|
||||||
|
<field name="res_model">cx.tower.git.repo</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
Create your first repository!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Repositories represent git repositories with their metadata and configuration.
|
||||||
|
They can be linked to remotes to automatically populate URL information.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
86
addons/cetmix_tower_git/views/cx_tower_git_source_views.xml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Tree View -->
|
||||||
|
<record id="cx_tower_git_source_view_tree" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.source.tree</field>
|
||||||
|
<field name="model">cx.tower.git.source</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree>
|
||||||
|
<field name="name" />
|
||||||
|
<field name="git_project_id" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Form View -->
|
||||||
|
<record id="cx_tower_git_source_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">cx.tower.git.source.form</field>
|
||||||
|
<field name="model">cx.tower.git.source</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<widget
|
||||||
|
name="web_ribbon"
|
||||||
|
title="Disabled"
|
||||||
|
bg_color="bg-danger"
|
||||||
|
attrs="{'invisible': [('enabled', '=', True)]}"
|
||||||
|
/>
|
||||||
|
<div class="oe_title">
|
||||||
|
<h1>
|
||||||
|
<field
|
||||||
|
name="name"
|
||||||
|
placeholder="Name. Leave blank to autogenerate"
|
||||||
|
/>
|
||||||
|
</h1>
|
||||||
|
<h3>
|
||||||
|
<field
|
||||||
|
name="reference"
|
||||||
|
placeholder="Reference. Can contain English letters, digits and '_'. Leave blank to autogenerate"
|
||||||
|
attrs="{'invisible': [('reference', '=', False)]}"
|
||||||
|
/>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<field name="sequence" />
|
||||||
|
<field name="enabled" />
|
||||||
|
<field name="active" invisible="1" />
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page name="remotes" string="Remotes">
|
||||||
|
<div
|
||||||
|
class="alert alert-warning"
|
||||||
|
role="alert"
|
||||||
|
style="margin-bottom:0px;"
|
||||||
|
attrs="{'invisible': [('remote_count', '<', 2)]}"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
The top one remote will be used as a merge target.
|
||||||
|
You can re-arrange remotes by dragging them or changing their sequence value.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<field name="remote_count" invisible="1" />
|
||||||
|
<field name="remote_ids">
|
||||||
|
<tree
|
||||||
|
decoration-muted="not enabled"
|
||||||
|
decoration-info="is_private == True"
|
||||||
|
>
|
||||||
|
<field name="sequence" widget="handle" />
|
||||||
|
<field name="repo_id" />
|
||||||
|
<field name="head_type" />
|
||||||
|
<field name="head" />
|
||||||
|
<field name="url_protocol" />
|
||||||
|
<field name="enabled" widget="boolean_toggle" />
|
||||||
|
<field name="is_private" optional="hide" />
|
||||||
|
<field name="reference" optional="hide" />
|
||||||
|
<field name="create_uid" optional="hide" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||