Compare commits
543 Commits
14.0
...
cetmix_tow
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
122
addons/cetmix_tower_server_queue/README.rst
Normal file
122
addons/cetmix_tower_server_queue/README.rst
Normal file
@@ -0,0 +1,122 @@
|
||||
=========================
|
||||
Cetmix Tower Server Queue
|
||||
=========================
|
||||
|
||||
..
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:bcdbf27340bb59ec9a0cf443b108e2d6b27cf7c64466b47585fbd02410ef071b
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |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_server_queue
|
||||
:alt: cetmix/cetmix-tower
|
||||
|
||||
|badge1| |badge2| |badge3|
|
||||
|
||||
This module implements asynchronous task execution for `Cetmix
|
||||
Tower <https://cetmix.com/tower>`__.
|
||||
|
||||
It requires the `queue_job <https://github.com/OCA/queue/queue_job>`__
|
||||
module to be installed and configured in the Odoo instance.
|
||||
|
||||
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.0 (2026-03-23)
|
||||
-----------------------
|
||||
|
||||
- Features: Jets! (4700)
|
||||
|
||||
16.0.1.2.0 (2025-11-12)
|
||||
-----------------------
|
||||
|
||||
- Features: Use the 'web_notify' module to send user notifications.
|
||||
(5074)
|
||||
|
||||
16.0.1.1.4 (2025-11-05)
|
||||
-----------------------
|
||||
|
||||
- Bugfixes: Finish multiple commands at once. (5062)
|
||||
|
||||
16.0.1.1.3 (2025-10-13)
|
||||
-----------------------
|
||||
|
||||
- Features: Terminate running flight plan manually (3410)
|
||||
|
||||
16.0.1.1.0 (2025-07-16)
|
||||
-----------------------
|
||||
|
||||
- Features: cetmix_tower_server_queue: Add async file upload/download
|
||||
via job queue (3720)
|
||||
- Features: Terminate command with error if job has failed (4718)
|
||||
|
||||
16.0.1.0.2 (2025-05-16)
|
||||
-----------------------
|
||||
|
||||
- Features: 'sudo' parameter is not passed to command. (4678)
|
||||
|
||||
16.0.1.0.1 (2025-05-09)
|
||||
-----------------------
|
||||
|
||||
- Bugfixes: Non-critical issues and performance improvements. (4611)
|
||||
|
||||
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_server_queue%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_server_queue>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute.
|
||||
1
addons/cetmix_tower_server_queue/__init__.py
Normal file
1
addons/cetmix_tower_server_queue/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
19
addons/cetmix_tower_server_queue/__manifest__.py
Normal file
19
addons/cetmix_tower_server_queue/__manifest__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright (C) 2022 Cetmix OÜ
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
{
|
||||
"name": "Cetmix Tower Server Queue",
|
||||
"summary": "Cetmix Tower asynchronous task execution using 'queue_job'",
|
||||
"version": "16.0.2.0.0",
|
||||
"development_status": "Beta",
|
||||
"category": "Productivity",
|
||||
"website": "https://tower.cetmix.com",
|
||||
"author": "Cetmix",
|
||||
"license": "AGPL-3",
|
||||
"installable": True,
|
||||
"auto_install": True,
|
||||
"depends": ["cetmix_tower_server", "queue_job"],
|
||||
"data": [
|
||||
"views/cx_tower_command_log_view.xml",
|
||||
"views/cx_tower_file_view.xml",
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * cetmix_tower_server_queue
|
||||
#
|
||||
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"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model.fields,help:cetmix_tower_server_queue.field_cx_tower_command_log__command_status
|
||||
msgid ""
|
||||
"0 if command finished successfully.\n"
|
||||
"-100 general error,\n"
|
||||
"-101 not found,\n"
|
||||
"-201 another instance of this command is running,\n"
|
||||
"-202 no runner found for the command action,\n"
|
||||
"-203 Python code execution failed\n"
|
||||
"-205 plan line condition check failed\n"
|
||||
"503 if SSH connection error occurred\n"
|
||||
"601 if queue job failed"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_command_log
|
||||
msgid "Cetmix Tower Command Log"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_file
|
||||
msgid "Cetmix Tower File"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_server
|
||||
msgid "Cetmix Tower Server"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_command_log__command_status
|
||||
msgid "Exit Code"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "Failure"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "File downloaded!"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model.fields,help:cetmix_tower_server_queue.field_cx_tower_file__is_being_processed
|
||||
msgid "File is currently being processed"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "File uploaded!"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "File(s) %(name)s download failed: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "File(s) %(name)s upload failed: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "Files downloaded!"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "Files uploaded!"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_file__is_being_processed
|
||||
msgid "Is Being Processed"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form
|
||||
msgid "Processing"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model,name:cetmix_tower_server_queue.model_queue_job
|
||||
#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_command_log__queue_job_id
|
||||
msgid "Queue Job"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form
|
||||
#, python-format
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "The following files are already being processed: %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Unable to upload file '%(f)s'.\n"
|
||||
"Upload operation is not supported for 'server' type files."
|
||||
msgstr ""
|
||||
148
addons/cetmix_tower_server_queue/i18n/it.po
Normal file
148
addons/cetmix_tower_server_queue/i18n/it.po
Normal file
@@ -0,0 +1,148 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * cetmix_tower_server_queue
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: it\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.3\n"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model.fields,help:cetmix_tower_server_queue.field_cx_tower_command_log__command_status
|
||||
msgid ""
|
||||
"0 if command finished successfully.\n"
|
||||
"-100 general error,\n"
|
||||
"-101 not found,\n"
|
||||
"-201 another instance of this command is running,\n"
|
||||
"-202 no runner found for the command action,\n"
|
||||
"-203 Python code execution failed\n"
|
||||
"-205 plan line condition check failed\n"
|
||||
"503 if SSH connection error occurred\n"
|
||||
"601 if queue job failed"
|
||||
msgstr "0 se il comando è stato completato correttamente.-100 errore generale,-101 non trovato,-201 un'altra istanza di questo comando è in esecuzione,-202 nessun runner trovato per l'azione del comando,-203 esecuzione del codice Python non riuscita,-205 controllo delle condizioni della riga del piano non riuscito,503 se si è verificato un errore di connessione SSH,601 se il processo in coda non è riuscito."
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_command_log
|
||||
msgid "Cetmix Tower Command Log"
|
||||
msgstr "Registro comando Cetmix Tower"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_file
|
||||
msgid "Cetmix Tower File"
|
||||
msgstr "File Cetmix Tower"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model,name:cetmix_tower_server_queue.model_cx_tower_server
|
||||
msgid "Cetmix Tower Server"
|
||||
msgstr "Server Cetmix Tower"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form
|
||||
msgid "Error"
|
||||
msgstr "Errore"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_command_log__command_status
|
||||
msgid "Exit Code"
|
||||
msgstr "Codice uscita"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "Failure"
|
||||
msgstr "Fallimento"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "File downloaded!"
|
||||
msgstr "File scaricato!"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model.fields,help:cetmix_tower_server_queue.field_cx_tower_file__is_being_processed
|
||||
msgid "File is currently being processed"
|
||||
msgstr "Il file è in lavorazione"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "File uploaded!"
|
||||
msgstr "File caricato!"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "Files downloaded!"
|
||||
msgstr "File scaricati!"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "Files uploaded!"
|
||||
msgstr "File caricati!"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_file__is_being_processed
|
||||
msgid "Is Being Processed"
|
||||
msgstr "In lavorazione"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form
|
||||
msgid "Processing"
|
||||
msgstr "Lavorazione"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#: model:ir.model,name:cetmix_tower_server_queue.model_queue_job
|
||||
#: model:ir.model.fields,field_description:cetmix_tower_server_queue.field_cx_tower_command_log__queue_job_id
|
||||
msgid "Queue Job"
|
||||
msgstr "Accoda lavoro"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#: model_terms:ir.ui.view,arch_db:cetmix_tower_server_queue.cx_tower_file_view_form
|
||||
#, python-format
|
||||
msgid "Success"
|
||||
msgstr "Successo"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid "The following files are already being processed: %(name)s"
|
||||
msgstr "I seguenti file sono già in fase di elaborazione: %(name)s"
|
||||
|
||||
#. module: cetmix_tower_server_queue
|
||||
#. odoo-python
|
||||
#: code:addons/cetmix_tower_server_queue/models/cx_tower_file.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Unable to upload file '%(f)s'.\n"
|
||||
"Upload operation is not supported for 'server' type files."
|
||||
msgstr ""
|
||||
"Impossibile caricare il file '%(f)s'.\n"
|
||||
"L'operazione di caricamento non è supportata per i file di tipo 'server'."
|
||||
|
||||
#~ msgid "Display Name"
|
||||
#~ msgstr "Nome visualizzato"
|
||||
|
||||
#~ msgid "ID"
|
||||
#~ msgstr "ID"
|
||||
|
||||
#~ msgid "Last Modified on"
|
||||
#~ msgstr "Ultima modifica il"
|
||||
4
addons/cetmix_tower_server_queue/models/__init__.py
Normal file
4
addons/cetmix_tower_server_queue/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import cx_tower_command_log
|
||||
from . import cx_tower_server
|
||||
from . import queue_job
|
||||
from . import cx_tower_file
|
||||
@@ -0,0 +1,82 @@
|
||||
# Copyright (C) 2025 Cetmix OÜ
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import fields, models, tools
|
||||
|
||||
from odoo.addons.cetmix_tower_server.models.constants import (
|
||||
COMMAND_STOPPED,
|
||||
COMMAND_TIMED_OUT,
|
||||
)
|
||||
from odoo.addons.queue_job.job import CANCELLED
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CxTowerCommandLog(models.Model):
|
||||
_inherit = "cx.tower.command.log"
|
||||
|
||||
queue_job_id = fields.Many2one(
|
||||
"queue.job",
|
||||
readonly=True,
|
||||
groups="queue_job.group_queue_job_manager",
|
||||
)
|
||||
|
||||
command_status = fields.Integer(
|
||||
help="0 if command finished successfully.\n"
|
||||
"-100 general error,\n"
|
||||
"-101 not found,\n"
|
||||
"-201 another instance of this command is running,\n"
|
||||
"-202 no runner found for the command action,\n"
|
||||
"-203 Python code execution failed\n"
|
||||
"-205 plan line condition check failed\n"
|
||||
"503 if SSH connection error occurred\n"
|
||||
"601 if queue job failed"
|
||||
)
|
||||
|
||||
def finish(
|
||||
self, finish_date=None, status=None, response=None, error=None, **kwargs
|
||||
):
|
||||
"""Finish the command log
|
||||
|
||||
Args:
|
||||
finish_date (Datetime, optional): Command finish date. Defaults to None.
|
||||
status (Integer, optional): Command status. Defaults to None.
|
||||
response (Text, optional): Command response. Defaults to None.
|
||||
error (Text, optional): Command error. Defaults to None.
|
||||
"""
|
||||
|
||||
# Filter out command logs that are already stopped
|
||||
command_logs_to_process = self.filtered(
|
||||
lambda log: log.command_status != COMMAND_STOPPED
|
||||
)
|
||||
if not command_logs_to_process:
|
||||
return
|
||||
|
||||
# Avoid finishing the command log multiple times at the same time
|
||||
try:
|
||||
with self.env.cr.savepoint(), tools.mute_logger("odoo.sql_db"):
|
||||
self.env.cr.execute(
|
||||
f"SELECT command_status FROM {self._table} WHERE id IN %s FOR UPDATE NOWAIT", # noqa: E501
|
||||
(tuple(command_logs_to_process.ids),),
|
||||
)
|
||||
except Exception as e:
|
||||
_logger.error(
|
||||
"Could not acquire lock on command logs %s, skipping finish: %s",
|
||||
command_logs_to_process.ids,
|
||||
e,
|
||||
)
|
||||
return
|
||||
|
||||
# Update the related queue job state if the command timed out
|
||||
if status == COMMAND_TIMED_OUT:
|
||||
for command_log in command_logs_to_process:
|
||||
if command_log.queue_job_id:
|
||||
command_log.queue_job_id.sudo()._change_job_state(
|
||||
CANCELLED, result=error
|
||||
)
|
||||
|
||||
return super(CxTowerCommandLog, command_logs_to_process).finish(
|
||||
finish_date, status, response, error, **kwargs
|
||||
)
|
||||
184
addons/cetmix_tower_server_queue/models/cx_tower_file.py
Normal file
184
addons/cetmix_tower_server_queue/models/cx_tower_file.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# Copyright (C) 2025 Cetmix OÜ
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CxTowerFile(models.Model):
|
||||
_inherit = "cx.tower.file"
|
||||
|
||||
is_being_processed = fields.Boolean(
|
||||
copy=False,
|
||||
help="File is currently being processed",
|
||||
)
|
||||
|
||||
def _check_files_being_processed(self, raise_error):
|
||||
"""
|
||||
Check if any file in the recordset is being processed.
|
||||
True if at least one file is already processing and raise_error is False.
|
||||
False if no files are currently being processed.
|
||||
The caller uses the boolean to decide whether to continue or abort.
|
||||
"""
|
||||
processing_files = self.filtered(lambda rec: rec.is_being_processed)
|
||||
if processing_files:
|
||||
if raise_error:
|
||||
raise UserError(
|
||||
_(
|
||||
"The following files are already being processed: %(name)s",
|
||||
name=", ".join(processing_files.mapped("name")),
|
||||
)
|
||||
)
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
def upload(self, raise_error=False):
|
||||
"""
|
||||
Trigger asynchronous upload via job queue.
|
||||
"""
|
||||
# Check if the file is already being processed
|
||||
if self._check_files_being_processed(raise_error):
|
||||
return
|
||||
|
||||
self.write({"server_response": False, "is_being_processed": True})
|
||||
|
||||
# Enqueue the upload if not already in a queue job;
|
||||
# otherwise, execute immediately
|
||||
if not self.env.context.get("job_uuid"):
|
||||
self.with_delay()._do_upload(raise_error=raise_error)
|
||||
else:
|
||||
self._do_upload(raise_error=raise_error)
|
||||
|
||||
def download(self, raise_error=False):
|
||||
"""
|
||||
Trigger asynchronous download via job queue.
|
||||
"""
|
||||
|
||||
# Check if the file is already being processed
|
||||
if self._check_files_being_processed(raise_error):
|
||||
return
|
||||
|
||||
self.write({"server_response": False, "is_being_processed": True})
|
||||
|
||||
# Enqueue the download if not already in a queue job;
|
||||
# otherwise, execute immediately
|
||||
if not self.env.context.get("job_uuid"):
|
||||
self.with_delay()._do_download(raise_error=raise_error)
|
||||
else:
|
||||
self._do_download(raise_error=raise_error)
|
||||
|
||||
def _do_upload(self, raise_error=True):
|
||||
"""
|
||||
Uploads the files within a job context and notifies the user on success.
|
||||
Logs the error if an exception occurs;
|
||||
failure state is managed by the parent method.
|
||||
"""
|
||||
try:
|
||||
with self.env.cr.savepoint():
|
||||
result = super().upload(raise_error=raise_error)
|
||||
single_msg = _("File uploaded!")
|
||||
plural_msg = _("Files uploaded!")
|
||||
self.env.user.notify_success(
|
||||
message=single_msg if len(self) == 1 else plural_msg,
|
||||
title=_("Success"),
|
||||
# This notification should not be sticky
|
||||
# to avoid blocking the user's screen
|
||||
sticky=False,
|
||||
)
|
||||
return result
|
||||
except Exception as e:
|
||||
if not raise_error:
|
||||
self.env.user.notify_danger(
|
||||
message=_(
|
||||
"File(s) %(name)s upload failed: %(error)s",
|
||||
name=", ".join(self.mapped("name")),
|
||||
error=str(e),
|
||||
),
|
||||
title=_("Failure"),
|
||||
sticky=self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("cetmix_tower_server.notification_type_error", "sticky")
|
||||
== "sticky",
|
||||
)
|
||||
_logger.error("File %s upload failed: %s", str(self), str(e))
|
||||
else:
|
||||
raise
|
||||
finally:
|
||||
self.write({"is_being_processed": False})
|
||||
|
||||
def _do_download(self, raise_error=True):
|
||||
"""
|
||||
Downloads the files within a job context and notifies the user on success.
|
||||
Logs the error if an exception occurs;
|
||||
failure state is managed by the parent method.
|
||||
"""
|
||||
try:
|
||||
with self.env.cr.savepoint():
|
||||
result = super().download(raise_error=raise_error)
|
||||
single_msg = _("File downloaded!")
|
||||
plural_msg = _("Files downloaded!")
|
||||
self.env.user.notify_success(
|
||||
message=single_msg if len(self) == 1 else plural_msg,
|
||||
title=_("Success"),
|
||||
# This notification should not be sticky
|
||||
# to avoid blocking the user's screen
|
||||
sticky=False,
|
||||
)
|
||||
return result
|
||||
except Exception as e:
|
||||
if not raise_error:
|
||||
self.env.user.notify_danger(
|
||||
message=_(
|
||||
"File(s) %(name)s download failed: %(error)s",
|
||||
name=", ".join(self.mapped("name")),
|
||||
error=str(e),
|
||||
),
|
||||
title=_("Failure"),
|
||||
sticky=self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("cetmix_tower_server.notification_type_error", "sticky")
|
||||
== "sticky",
|
||||
)
|
||||
_logger.error("File %s download failed: %s", str(self), str(e))
|
||||
else:
|
||||
raise
|
||||
finally:
|
||||
self.write({"is_being_processed": False})
|
||||
|
||||
def action_pull_from_server(self):
|
||||
"""
|
||||
Pull file from server without notification.
|
||||
"""
|
||||
tower_files = self.filtered(lambda file_: file_.source == "tower")
|
||||
server_files = self - tower_files
|
||||
|
||||
tower_files.action_get_current_server_code()
|
||||
|
||||
server_files.download(raise_error=False)
|
||||
|
||||
def action_push_to_server(self):
|
||||
"""
|
||||
Push the file to server without success notification.
|
||||
"""
|
||||
server_files = self.filtered(lambda file_: file_.source == "server")
|
||||
if server_files:
|
||||
return {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "display_notification",
|
||||
"params": {
|
||||
"title": _("Failure"),
|
||||
"message": _(
|
||||
"Unable to upload file '%(f)s'.\n"
|
||||
"Upload operation is not supported for 'server' type files.",
|
||||
f=", ".join(server_files.mapped("rendered_name")),
|
||||
),
|
||||
"sticky": False,
|
||||
},
|
||||
}
|
||||
|
||||
self.upload(raise_error=False)
|
||||
86
addons/cetmix_tower_server_queue/models/cx_tower_server.py
Normal file
86
addons/cetmix_tower_server_queue/models/cx_tower_server.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# Copyright (C) 2022 Cetmix OÜ
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from odoo import models
|
||||
|
||||
|
||||
class CxTowerServer(models.Model):
|
||||
_inherit = "cx.tower.server"
|
||||
|
||||
def _command_runner_wrapper(
|
||||
self,
|
||||
command,
|
||||
log_record,
|
||||
rendered_command_code,
|
||||
sudo=None,
|
||||
rendered_command_path=None,
|
||||
ssh_connection=None,
|
||||
**kwargs,
|
||||
):
|
||||
# If the flight plan log has an entry on the parent flight plan log,
|
||||
# it means that this flight plan was launched from another plan,
|
||||
# this plan should be launched as a synchronous command to
|
||||
# preserve the order of execution of commands with actions
|
||||
# "Run Flight Plan", "Trigger Jet Action" and "Create Waypoint".
|
||||
# Use runner only if command log record is provided.
|
||||
if (
|
||||
log_record
|
||||
and not log_record.plan_log_id.parent_flight_plan_log_id
|
||||
and command.action
|
||||
not in [
|
||||
"jet_action",
|
||||
"create_waypoint",
|
||||
]
|
||||
):
|
||||
job = self.with_delay()._queue_command_runner_wrapper(
|
||||
command=command,
|
||||
log_record=log_record,
|
||||
rendered_command_code=rendered_command_code,
|
||||
sudo=sudo,
|
||||
rendered_command_path=rendered_command_path,
|
||||
ssh_connection=ssh_connection,
|
||||
**kwargs,
|
||||
)
|
||||
log_record.sudo().queue_job_id = job.db_record().id
|
||||
|
||||
# Otherwise fallback to `super` to return the command output
|
||||
else:
|
||||
return super()._command_runner_wrapper(
|
||||
command=command,
|
||||
log_record=log_record,
|
||||
rendered_command_code=rendered_command_code,
|
||||
sudo=sudo,
|
||||
rendered_command_path=rendered_command_path,
|
||||
ssh_connection=ssh_connection,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def _queue_command_runner_wrapper(
|
||||
self,
|
||||
command,
|
||||
log_record,
|
||||
rendered_command_code,
|
||||
sudo=None,
|
||||
rendered_command_path=None,
|
||||
ssh_connection=None,
|
||||
**kwargs,
|
||||
):
|
||||
# avoid executing command if plan was stopped
|
||||
log_record.invalidate_recordset(["plan_log_id"])
|
||||
plan_log_id = log_record.plan_log_id
|
||||
if plan_log_id:
|
||||
plan_log_id.invalidate_recordset(["is_stopped"])
|
||||
|
||||
# If plan was stopped, stop the command
|
||||
if plan_log_id.is_stopped:
|
||||
log_record.stop()
|
||||
return
|
||||
|
||||
return self._command_runner(
|
||||
command=command,
|
||||
log_record=log_record,
|
||||
rendered_command_code=rendered_command_code,
|
||||
sudo=sudo,
|
||||
rendered_command_path=rendered_command_path,
|
||||
ssh_connection=ssh_connection,
|
||||
**kwargs,
|
||||
)
|
||||
23
addons/cetmix_tower_server_queue/models/queue_job.py
Normal file
23
addons/cetmix_tower_server_queue/models/queue_job.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Copyright 2013-2020 Camptocamp SA
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
||||
from odoo import models
|
||||
|
||||
|
||||
class QueueJob(models.Model):
|
||||
_inherit = "queue.job"
|
||||
|
||||
QUEUE_JOB_ERROR = 601
|
||||
|
||||
def write(self, vals):
|
||||
"""
|
||||
Override write method to update command status
|
||||
and write error information in the log record
|
||||
"""
|
||||
if vals.get("state") == "failed":
|
||||
log_record = self.kwargs.get("log_record")
|
||||
if log_record:
|
||||
log_record.finish(
|
||||
status=self.QUEUE_JOB_ERROR,
|
||||
error=vals.get("exc_info"),
|
||||
)
|
||||
return super().write(vals)
|
||||
3
addons/cetmix_tower_server_queue/pyproject.toml
Normal file
3
addons/cetmix_tower_server_queue/pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["whool"]
|
||||
build-backend = "whool.buildapi"
|
||||
1
addons/cetmix_tower_server_queue/readme/CONFIGURE.md
Normal file
1
addons/cetmix_tower_server_queue/readme/CONFIGURE.md
Normal file
@@ -0,0 +1 @@
|
||||
Please refer to the [official documentation](https://cetmix.com/tower) for detailed configuration instructions.
|
||||
5
addons/cetmix_tower_server_queue/readme/DESCRIPTION.md
Normal file
5
addons/cetmix_tower_server_queue/readme/DESCRIPTION.md
Normal file
@@ -0,0 +1,5 @@
|
||||
This module implements asynchronous task execution for [Cetmix Tower](https://cetmix.com/tower).
|
||||
|
||||
It requires the [queue_job](https://github.com/OCA/queue/queue_job) module to be installed and configured in the Odoo instance.
|
||||
|
||||
Please refer to the [official documentation](https://cetmix.com/tower) for detailed information.
|
||||
39
addons/cetmix_tower_server_queue/readme/HISTORY.md
Normal file
39
addons/cetmix_tower_server_queue/readme/HISTORY.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## 16.0.2.0.0 (2026-03-23)
|
||||
|
||||
- Features: Jets! (4700)
|
||||
|
||||
|
||||
## 16.0.1.2.0 (2025-11-12)
|
||||
|
||||
- Features: Use the 'web_notify' module to send user notifications. (5074)
|
||||
|
||||
|
||||
## 16.0.1.1.4 (2025-11-05)
|
||||
|
||||
- Bugfixes: Finish multiple commands at once. (5062)
|
||||
|
||||
|
||||
## 16.0.1.1.3 (2025-10-13)
|
||||
|
||||
- Features: Terminate running flight plan manually (3410)
|
||||
|
||||
|
||||
## 16.0.1.1.0 (2025-07-16)
|
||||
|
||||
- Features: cetmix_tower_server_queue: Add async file upload/download via job queue (3720)
|
||||
- Features: Terminate command with error if job has failed (4718)
|
||||
|
||||
|
||||
## 16.0.1.0.2 (2025-05-16)
|
||||
|
||||
- Features: 'sudo' parameter is not passed to command. (4678)
|
||||
|
||||
|
||||
## 16.0.1.0.1 (2025-05-09)
|
||||
|
||||
- Bugfixes: Non-critical issues and performance improvements. (4611)
|
||||
|
||||
|
||||
## 16.0.1.0.0
|
||||
|
||||
Release for Odoo 16.0
|
||||
1
addons/cetmix_tower_server_queue/readme/USAGE.md
Normal file
1
addons/cetmix_tower_server_queue/readme/USAGE.md
Normal file
@@ -0,0 +1 @@
|
||||
Please refer to the [official documentation](https://cetmix.com/tower) for detailed usage instructions.
|
||||
BIN
addons/cetmix_tower_server_queue/static/description/icon.png
Normal file
BIN
addons/cetmix_tower_server_queue/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
491
addons/cetmix_tower_server_queue/static/description/index.html
Normal file
491
addons/cetmix_tower_server_queue/static/description/index.html
Normal file
@@ -0,0 +1,491 @@
|
||||
<!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 Server Queue</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-server-queue">
|
||||
<h1 class="title">Cetmix Tower Server Queue</h1>
|
||||
|
||||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:bcdbf27340bb59ec9a0cf443b108e2d6b27cf7c64466b47585fbd02410ef071b
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<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_server_queue"><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 asynchronous task execution for <a class="reference external" href="https://cetmix.com/tower">Cetmix
|
||||
Tower</a>.</p>
|
||||
<p>It requires the <a class="reference external" href="https://github.com/OCA/queue/queue_job">queue_job</a>
|
||||
module to be installed and configured in the Odoo instance.</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.0 (2026-03-23)</a></li>
|
||||
<li><a class="reference internal" href="#section-2" id="toc-entry-5">16.0.1.2.0 (2025-11-12)</a></li>
|
||||
<li><a class="reference internal" href="#section-3" id="toc-entry-6">16.0.1.1.4 (2025-11-05)</a></li>
|
||||
<li><a class="reference internal" href="#section-4" id="toc-entry-7">16.0.1.1.3 (2025-10-13)</a></li>
|
||||
<li><a class="reference internal" href="#section-5" id="toc-entry-8">16.0.1.1.0 (2025-07-16)</a></li>
|
||||
<li><a class="reference internal" href="#section-6" id="toc-entry-9">16.0.1.0.2 (2025-05-16)</a></li>
|
||||
<li><a class="reference internal" href="#section-7" id="toc-entry-10">16.0.1.0.1 (2025-05-09)</a></li>
|
||||
<li><a class="reference internal" href="#section-8" id="toc-entry-11">16.0.1.0.0</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-12">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="toc-entry-13">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="toc-entry-14">Authors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="toc-entry-15">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.0 (2026-03-23)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Features: Jets! (4700)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-2">
|
||||
<h2><a class="toc-backref" href="#toc-entry-5">16.0.1.2.0 (2025-11-12)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Features: Use the ‘web_notify’ module to send user notifications.
|
||||
(5074)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-3">
|
||||
<h2><a class="toc-backref" href="#toc-entry-6">16.0.1.1.4 (2025-11-05)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Bugfixes: Finish multiple commands at once. (5062)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-4">
|
||||
<h2><a class="toc-backref" href="#toc-entry-7">16.0.1.1.3 (2025-10-13)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Features: Terminate running flight plan manually (3410)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-5">
|
||||
<h2><a class="toc-backref" href="#toc-entry-8">16.0.1.1.0 (2025-07-16)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Features: cetmix_tower_server_queue: Add async file upload/download
|
||||
via job queue (3720)</li>
|
||||
<li>Features: Terminate command with error if job has failed (4718)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-6">
|
||||
<h2><a class="toc-backref" href="#toc-entry-9">16.0.1.0.2 (2025-05-16)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Features: ‘sudo’ parameter is not passed to command. (4678)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-7">
|
||||
<h2><a class="toc-backref" href="#toc-entry-10">16.0.1.0.1 (2025-05-09)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Bugfixes: Non-critical issues and performance improvements. (4611)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="section-8">
|
||||
<h2><a class="toc-backref" href="#toc-entry-11">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-12">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_server_queue%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-13">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#toc-entry-14">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Cetmix</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#toc-entry-15">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_server_queue">cetmix/cetmix-tower</a> project on GitHub.</p>
|
||||
<p>You are welcome to contribute.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
3
addons/cetmix_tower_server_queue/tests/__init__.py
Normal file
3
addons/cetmix_tower_server_queue/tests/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import test_command
|
||||
from . import test_command_log
|
||||
from . import test_file
|
||||
145
addons/cetmix_tower_server_queue/tests/test_command.py
Normal file
145
addons/cetmix_tower_server_queue/tests/test_command.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.fields import Datetime
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon
|
||||
|
||||
|
||||
class TestTowerCommand(TestTowerCommon):
|
||||
"""Test suite for verifying zombie command detection and related
|
||||
queue job cancellation.
|
||||
|
||||
Tests in this class verify that commands which have been running
|
||||
longer than the timeout are properly detected as zombies, and their
|
||||
associated queue jobs are cancelled.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
# Set command timeout to 10 seconds
|
||||
cls.env["ir.config_parameter"].sudo().set_param(
|
||||
"cetmix_tower_server.command_timeout", "10"
|
||||
)
|
||||
# Set old time to 20 seconds ago (older than timeout)
|
||||
# to simulate running command in past
|
||||
now = Datetime.now()
|
||||
cls.old_time = now - timedelta(seconds=20)
|
||||
|
||||
def _patch_command_runner(self, command_type, runner_method):
|
||||
"""Helper to patch a command runner to simulate a zombie command.
|
||||
|
||||
Args:
|
||||
command_type: Type of command runner to patch ('ssh' or 'python_code')
|
||||
runner_method: Original method to wrap
|
||||
|
||||
Returns:
|
||||
A context manager that applies the patch
|
||||
"""
|
||||
|
||||
def _wrapper(*args, **kwargs):
|
||||
# Modify args to disable log record finishing
|
||||
args = list(args)
|
||||
if len(args) > 1:
|
||||
args[1] = False # Set log_record to False
|
||||
return runner_method(*args, **kwargs)
|
||||
|
||||
return patch.object(
|
||||
self.registry["cx.tower.server"],
|
||||
f"_command_runner_{command_type}",
|
||||
_wrapper,
|
||||
)
|
||||
|
||||
def _verify_zombie_command_job_cancellation(self, command_action):
|
||||
"""Verify zombie command is detected and job is cancelled.
|
||||
|
||||
Args:
|
||||
command_action: Action type ('ssh_command' or 'python_code')
|
||||
"""
|
||||
# check zombie command logs
|
||||
domain = [
|
||||
("is_running", "=", True),
|
||||
("start_date", "=", self.old_time),
|
||||
("command_action", "=", command_action),
|
||||
]
|
||||
zombie_command_logs = self.env["cx.tower.command.log"].search(domain)
|
||||
|
||||
self.assertEqual(
|
||||
len(zombie_command_logs), 1, "Zombie command log should be created"
|
||||
)
|
||||
self.assertTrue(
|
||||
zombie_command_logs.queue_job_id,
|
||||
"Zombie command log should have queue job",
|
||||
)
|
||||
|
||||
job = zombie_command_logs.queue_job_id
|
||||
self.assertTrue(job.exists(), "Zombie command job should exist")
|
||||
|
||||
self.assertEqual(job.state, "pending", "Zombie command job should be pending")
|
||||
|
||||
# run process to kill zombie command
|
||||
self.server_test_1._check_zombie_commands()
|
||||
|
||||
# check that command log is cancelled
|
||||
self.assertEqual(
|
||||
job.state, "cancelled", "Zombie command job should be cancelled"
|
||||
)
|
||||
|
||||
def test_check_zombie_ssh_command_queue(self):
|
||||
"""
|
||||
Test that zombie ssh command is killed and job is cancelled
|
||||
"""
|
||||
# Create test commands
|
||||
ssh_command = self.Command.create(
|
||||
{
|
||||
"name": "Test SSH Command",
|
||||
"code": "ls -la",
|
||||
"action": "ssh_command",
|
||||
}
|
||||
)
|
||||
|
||||
# patch command runner to not finish log record
|
||||
cx_tower_server_obj = self.registry["cx.tower.server"]
|
||||
_command_runner_ssh_super = cx_tower_server_obj._command_runner_ssh
|
||||
|
||||
with self._patch_command_runner("ssh", _command_runner_ssh_super):
|
||||
# run zombie command with log creation in past
|
||||
self.server_test_1.run_command(
|
||||
ssh_command, log={"start_date": self.old_time}
|
||||
)
|
||||
|
||||
# check zombie command logs
|
||||
self._verify_zombie_command_job_cancellation("ssh_command")
|
||||
|
||||
@mute_logger("py.warnings")
|
||||
def test_check_zombie_python_command_queue(self):
|
||||
"""
|
||||
Test that zombie python command is killed and job is cancelled
|
||||
"""
|
||||
# Create test commands
|
||||
python_command = self.Command.create(
|
||||
{
|
||||
"name": "Test Python Command",
|
||||
"code": "print('test')",
|
||||
"action": "python_code",
|
||||
}
|
||||
)
|
||||
|
||||
# patch command runner to not finish log record
|
||||
cx_tower_server_obj = self.registry["cx.tower.server"]
|
||||
_command_runner_python_code_super = (
|
||||
cx_tower_server_obj._command_runner_python_code
|
||||
)
|
||||
|
||||
with self._patch_command_runner(
|
||||
"python_code", _command_runner_python_code_super
|
||||
):
|
||||
# run zombie command with log creation in past
|
||||
self.server_test_1.run_command(
|
||||
python_command, log={"start_date": self.old_time}
|
||||
)
|
||||
|
||||
# check zombie command logs
|
||||
self._verify_zombie_command_job_cancellation("python_code")
|
||||
37
addons/cetmix_tower_server_queue/tests/test_command_log.py
Normal file
37
addons/cetmix_tower_server_queue/tests/test_command_log.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon
|
||||
from odoo.addons.queue_job.job import Job
|
||||
|
||||
|
||||
class TestTowerCommand(TestTowerCommon):
|
||||
"""
|
||||
Test cases for command log state on queue_job failure
|
||||
"""
|
||||
|
||||
def test_command_log_state_on_job_fail(self):
|
||||
command = self.env["cx.tower.command"].create(
|
||||
{
|
||||
"name": "Test Command",
|
||||
"action": "ssh_command",
|
||||
"code": "echo 'Hello World'",
|
||||
}
|
||||
)
|
||||
self.assertTrue(command.id, "Command should be created successfully")
|
||||
|
||||
self.server_test_1.run_command(command=command)
|
||||
command_log = self.env["cx.tower.command.log"].search(
|
||||
[("command_id", "=", command.id)], order="id desc", limit=1
|
||||
)
|
||||
self.assertTrue(command_log, "Command log should be created")
|
||||
|
||||
job = command_log.queue_job_id
|
||||
self.assertTrue(job, "Queue job should be associated with command log")
|
||||
|
||||
job_obj = Job.load(self.env, job.uuid)
|
||||
job_obj.set_failed()
|
||||
job_obj.store()
|
||||
self.assertEqual(job.state, "failed", "Job should be in failed state")
|
||||
self.assertEqual(
|
||||
command_log.command_status,
|
||||
self.env["queue.job"].QUEUE_JOB_ERROR,
|
||||
"Command log should be in failed state",
|
||||
)
|
||||
201
addons/cetmix_tower_server_queue/tests/test_file.py
Normal file
201
addons/cetmix_tower_server_queue/tests/test_file.py
Normal file
@@ -0,0 +1,201 @@
|
||||
from odoo import exceptions
|
||||
|
||||
from odoo.addons.cetmix_tower_server.tests.common import TestTowerCommon
|
||||
from odoo.addons.queue_job.tests.common import trap_jobs
|
||||
|
||||
|
||||
class TestCxTowerFileQueue(TestTowerCommon):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.file_template = self.FileTemplate.create(
|
||||
{
|
||||
"name": "Test",
|
||||
"file_name": "test.txt",
|
||||
"server_dir": "/var/tmp",
|
||||
"code": "Hello, world!",
|
||||
}
|
||||
)
|
||||
|
||||
def test_async_upload_operations(self):
|
||||
"""Test that upload operations are processed asynchronously"""
|
||||
# Create unique files specifically for this test
|
||||
upload_file = self.File.create(
|
||||
{
|
||||
"source": "tower",
|
||||
"template_id": self.file_template.id,
|
||||
"server_id": self.server_test_1.id,
|
||||
"name": "upload_test_1",
|
||||
"auto_sync": False,
|
||||
}
|
||||
)
|
||||
|
||||
upload_file_2 = self.File.create(
|
||||
{
|
||||
"name": "upload_test_2",
|
||||
"source": "server",
|
||||
"server_id": self.server_test_1.id,
|
||||
"server_dir": "/var/tmp",
|
||||
"auto_sync": False,
|
||||
}
|
||||
)
|
||||
|
||||
with trap_jobs() as trap:
|
||||
upload_file.upload()
|
||||
upload_file_2.upload()
|
||||
|
||||
self.assertEqual(len(trap.enqueued_jobs), 2)
|
||||
|
||||
upload_file.write({"server_response": "ok", "is_being_processed": False})
|
||||
upload_file_2.write({"server_response": "ok", "is_being_processed": False})
|
||||
|
||||
# Refresh records to get updated values
|
||||
upload_file.invalidate_recordset()
|
||||
upload_file_2.invalidate_recordset()
|
||||
|
||||
# Verify the expected state
|
||||
self.assertEqual(upload_file.server_response, "ok")
|
||||
self.assertFalse(upload_file.is_being_processed)
|
||||
|
||||
self.assertEqual(upload_file_2.server_response, "ok")
|
||||
self.assertFalse(upload_file_2.is_being_processed)
|
||||
|
||||
def test_async_download_operations(self):
|
||||
"""Test that download operations are processed asynchronously"""
|
||||
# Create unique files specifically for this test
|
||||
download_file = self.File.create(
|
||||
{
|
||||
"source": "tower",
|
||||
"template_id": self.file_template.id,
|
||||
"server_id": self.server_test_1.id,
|
||||
"name": "download_test_1",
|
||||
"auto_sync": False,
|
||||
}
|
||||
)
|
||||
|
||||
download_file_2 = self.File.create(
|
||||
{
|
||||
"name": "download_test_2",
|
||||
"source": "server",
|
||||
"server_id": self.server_test_1.id,
|
||||
"server_dir": "/var/tmp",
|
||||
"auto_sync": False,
|
||||
}
|
||||
)
|
||||
|
||||
with trap_jobs() as trap:
|
||||
download_file.download()
|
||||
download_file_2.download()
|
||||
|
||||
# Verify jobs were created
|
||||
self.assertEqual(len(trap.enqueued_jobs), 2)
|
||||
|
||||
download_file.write({"server_response": "ok", "is_being_processed": False})
|
||||
download_file_2.write(
|
||||
{"server_response": "ok", "is_being_processed": False}
|
||||
)
|
||||
|
||||
# Refresh records to get updated values
|
||||
download_file.invalidate_recordset()
|
||||
download_file_2.invalidate_recordset()
|
||||
|
||||
# Verify the expected state
|
||||
self.assertEqual(download_file.server_response, "ok")
|
||||
self.assertFalse(download_file.is_being_processed)
|
||||
|
||||
self.assertEqual(download_file_2.server_response, "ok")
|
||||
self.assertFalse(download_file_2.is_being_processed)
|
||||
|
||||
def test_upload_error_handling(self):
|
||||
"""Test error handling in async upload operations"""
|
||||
error_file = self.File.create(
|
||||
{
|
||||
"source": "tower",
|
||||
"template_id": self.file_template.id,
|
||||
"server_id": self.server_test_1.id,
|
||||
"name": "error_handling_test",
|
||||
"auto_sync": False,
|
||||
}
|
||||
)
|
||||
|
||||
# Set context to force the mock in ssh_upload_file to raise error
|
||||
error_context = {"raise_upload_error": "Forced upload error"}
|
||||
|
||||
with trap_jobs() as trap:
|
||||
# This will trigger job creation but the job would fail if executed
|
||||
error_file.with_context(**error_context).upload(raise_error=True)
|
||||
|
||||
# Verify job was created
|
||||
self.assertEqual(len(trap.enqueued_jobs), 1)
|
||||
|
||||
# Simulate what would happen if the job executed and failed
|
||||
error_file.write({"server_response": "error", "is_being_processed": False})
|
||||
error_file.invalidate_recordset()
|
||||
|
||||
self.assertEqual(error_file.server_response, "error")
|
||||
self.assertFalse(error_file.is_being_processed)
|
||||
|
||||
def test_download_error_handling(self):
|
||||
"""Test error handling in async download operations"""
|
||||
error_file = self.File.create(
|
||||
{
|
||||
"source": "server",
|
||||
"server_id": self.server_test_1.id,
|
||||
"server_dir": "/var/tmp",
|
||||
"name": "download_error_test",
|
||||
}
|
||||
)
|
||||
|
||||
# Set context to force the mock in ssh_download_file to raise error
|
||||
error_context = {"raise_download_error": "Forced download error"}
|
||||
|
||||
with trap_jobs() as trap:
|
||||
# This will trigger job creation but the job would fail if executed
|
||||
error_file.with_context(**error_context).download(raise_error=True)
|
||||
|
||||
# Verify job was created
|
||||
self.assertEqual(len(trap.enqueued_jobs), 1)
|
||||
|
||||
# Simulate what would happen if the job executed and failed
|
||||
error_file.write({"server_response": "error", "is_being_processed": False})
|
||||
error_file.invalidate_recordset()
|
||||
|
||||
self.assertEqual(error_file.server_response, "error")
|
||||
self.assertFalse(error_file.is_being_processed)
|
||||
|
||||
def test_already_processing_check(self):
|
||||
"""Test that files being processed cannot be processed again"""
|
||||
processing_file = self.File.create(
|
||||
{
|
||||
"source": "tower",
|
||||
"template_id": self.file_template.id,
|
||||
"server_id": self.server_test_1.id,
|
||||
"name": "processing_test_file",
|
||||
"is_being_processed": True,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(processing_file.is_being_processed)
|
||||
|
||||
# Test with raising error
|
||||
with self.assertRaises(exceptions.UserError):
|
||||
processing_file.upload(raise_error=True)
|
||||
|
||||
# Test without raising error - should not create job
|
||||
with trap_jobs() as trap:
|
||||
processing_file.upload(raise_error=False)
|
||||
# No job should be created since file is already being processed
|
||||
self.assertEqual(len(trap.enqueued_jobs), 0)
|
||||
|
||||
# Verify still marked as processing
|
||||
self.assertTrue(processing_file.is_being_processed)
|
||||
|
||||
# Same tests for download
|
||||
with self.assertRaises(exceptions.UserError):
|
||||
processing_file.download(raise_error=True)
|
||||
|
||||
with trap_jobs() as trap:
|
||||
processing_file.download(raise_error=False)
|
||||
# No job should be created
|
||||
self.assertEqual(len(trap.enqueued_jobs), 0)
|
||||
|
||||
self.assertTrue(processing_file.is_being_processed)
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="cx_tower_command_log_view_form" model="ir.ui.view">
|
||||
<field name="name">cx.tower.command.log.view.form</field>
|
||||
<field name="model">cx.tower.command.log</field>
|
||||
<field
|
||||
name="inherit_id"
|
||||
ref="cetmix_tower_server.cx_tower_command_log_view_form"
|
||||
/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='command_id']" position="after">
|
||||
<field
|
||||
name="queue_job_id"
|
||||
attrs="{'invisible': [('queue_job_id', '=', False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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">
|
||||
<xpath expr="//form/sheet/group" position="before">
|
||||
<field name="is_being_processed" invisible="1" />
|
||||
<field name="server_response" invisible="1" />
|
||||
<widget
|
||||
name="web_ribbon"
|
||||
title="Processing"
|
||||
bg_color="bg-info"
|
||||
attrs="{'invisible': [('is_being_processed', '=', False)]}"
|
||||
/>
|
||||
<widget
|
||||
name="web_ribbon"
|
||||
title="Success"
|
||||
bg_color="bg-success"
|
||||
attrs="{'invisible': ['|', ('is_being_processed', '=', True), ('server_response', '!=', 'ok')]}"
|
||||
/>
|
||||
<widget
|
||||
name="web_ribbon"
|
||||
title="Error"
|
||||
bg_color="bg-danger"
|
||||
attrs="{'invisible': ['|', ('is_being_processed', '=', True), ('server_response', 'in', ('ok', False))]}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="cx_tower_queue_file_view_tree" model="ir.ui.view">
|
||||
<field name="name">cx.tower.queue.file.view.tree</field>
|
||||
<field name="model">cx.tower.file</field>
|
||||
<field name="inherit_id" ref="cetmix_tower_server.cx_tower_file_view_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree" position="inside">
|
||||
<field name="is_being_processed" invisible="1" />
|
||||
<field name="server_response" invisible="1" />
|
||||
</xpath>
|
||||
<xpath expr="//tree" position="attributes">
|
||||
<attribute name="decoration-info">
|
||||
is_being_processed == True
|
||||
</attribute>
|
||||
<attribute name="decoration-success">
|
||||
is_being_processed != True and server_response == 'ok'
|
||||
</attribute>
|
||||
<attribute name="decoration-danger">
|
||||
is_being_processed != True and server_response not in ('ok', False)
|
||||
</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user