Wipe addons/: full reset for clean re-upload

This commit is contained in:
Tower Deploy
2026-04-27 11:20:53 +03:00
parent 2cf3b5185d
commit 9bb80002c8
363 changed files with 0 additions and 112641 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

View File

@@ -1,801 +0,0 @@
<!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</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">
<h1 class="title">Cetmix Tower Server</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:4e04c56ceb53a86825bfbc09ed6acd8bbcaa032e85cef16c43997437620f429d
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<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"><img alt="cetmix/cetmix-tower" src="https://img.shields.io/badge/github-cetmix%2Fcetmix--tower-lightgray.png?logo=github" /></a></p>
<p><a class="reference external" href="https://cetmix.com/tower">Cetmix Tower</a> offers a streamlined
solution for managing remote servers and applications via SSH or API
calls directly from <a class="reference external" href="https://odoo.com">Odoo</a>. It is designed for
versatility across different operating systems and software
environments, providing a practical option for those looking to manage
servers without getting tied down by vendor or technology constraints.</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.3.0.1 (2026-03-27)</a></li>
<li><a class="reference internal" href="#section-2" id="toc-entry-5">16.0.3.0.0 (2026-03-23)</a></li>
<li><a class="reference internal" href="#section-3" id="toc-entry-6">16.0.2.2.14 (2026-02-17)</a></li>
<li><a class="reference internal" href="#section-4" id="toc-entry-7">16.0.2.2.13 (2026-01-12)</a></li>
<li><a class="reference internal" href="#section-5" id="toc-entry-8">16.0.2.2.12 (2026-01-11)</a></li>
<li><a class="reference internal" href="#section-6" id="toc-entry-9">16.0.2.2.11 (2026-01-08)</a></li>
<li><a class="reference internal" href="#section-7" id="toc-entry-10">16.0.2.2.10 (2026-01-08)</a></li>
<li><a class="reference internal" href="#section-8" id="toc-entry-11">16.0.2.2.8 (2025-12-22)</a></li>
<li><a class="reference internal" href="#section-9" id="toc-entry-12">16.0.2.2.7 (2025-12-16)</a></li>
<li><a class="reference internal" href="#section-10" id="toc-entry-13">16.0.2.2.6 (2025-12-11)</a></li>
<li><a class="reference internal" href="#section-11" id="toc-entry-14">16.0.2.2.5 (2025-12-10)</a></li>
<li><a class="reference internal" href="#section-12" id="toc-entry-15">16.0.2.2.4 (2025-12-10)</a></li>
<li><a class="reference internal" href="#section-13" id="toc-entry-16">16.0.2.2.3 (2025-12-03)</a></li>
<li><a class="reference internal" href="#section-14" id="toc-entry-17">16.0.2.2.2 (2025-12-03)</a></li>
<li><a class="reference internal" href="#section-15" id="toc-entry-18">16.0.2.2.0 (2025-11-12)</a></li>
<li><a class="reference internal" href="#section-16" id="toc-entry-19">16.0.2.0.6 (2025-10-27)</a></li>
<li><a class="reference internal" href="#section-17" id="toc-entry-20">16.0.2.0.5 (2025-10-16)</a></li>
<li><a class="reference internal" href="#section-18" id="toc-entry-21">16.0.2.0.4 (2025-10-13)</a></li>
<li><a class="reference internal" href="#section-19" id="toc-entry-22">16.0.2.0.3 (2025-10-13)</a></li>
<li><a class="reference internal" href="#section-20" id="toc-entry-23">16.0.2.0.2 (2025-10-08)</a></li>
<li><a class="reference internal" href="#section-21" id="toc-entry-24">16.0.2.0.1 (2025-10-08)</a></li>
<li><a class="reference internal" href="#section-22" id="toc-entry-25">16.0.2.0.0 (2025-10-07)</a></li>
<li><a class="reference internal" href="#section-23" id="toc-entry-26">16.0.1.7.2 (2025-09-18)</a></li>
<li><a class="reference internal" href="#section-24" id="toc-entry-27">16.0.1.7.1 (2025-09-10)</a></li>
<li><a class="reference internal" href="#section-25" id="toc-entry-28">16.0.1.6.4 (2025-08-18)</a></li>
<li><a class="reference internal" href="#section-26" id="toc-entry-29">16.0.1.6.3 (2025-08-13)</a></li>
<li><a class="reference internal" href="#section-27" id="toc-entry-30">16.0.1.6.2 (2025-08-05)</a></li>
<li><a class="reference internal" href="#section-28" id="toc-entry-31">16.0.1.6.0 (2025-07-30)</a></li>
<li><a class="reference internal" href="#section-29" id="toc-entry-32">16.0.1.5.3 (2025-07-29)</a></li>
<li><a class="reference internal" href="#section-30" id="toc-entry-33">16.0.1.5.1 (2025-07-25)</a></li>
<li><a class="reference internal" href="#section-31" id="toc-entry-34">16.0.1.5.0 (2025-07-22)</a></li>
<li><a class="reference internal" href="#section-32" id="toc-entry-35">16.0.1.3.0 (2025-07-17)</a></li>
<li><a class="reference internal" href="#section-33" id="toc-entry-36">16.0.1.1.4 (2025-07-07)</a></li>
<li><a class="reference internal" href="#section-34" id="toc-entry-37">16.0.1.1.2 (2025-06-25)</a></li>
<li><a class="reference internal" href="#section-35" id="toc-entry-38">16.0.1.1.1 (2025-06-21)</a></li>
<li><a class="reference internal" href="#section-36" id="toc-entry-39">16.0.1.1.0 (2025-06-20)</a></li>
<li><a class="reference internal" href="#section-37" id="toc-entry-40">16.0.1.0.12 (2025-06-06)</a></li>
<li><a class="reference internal" href="#section-38" id="toc-entry-41">16.0.1.0.11 (2025-06-06)</a></li>
<li><a class="reference internal" href="#section-39" id="toc-entry-42">16.0.1.0.10 (2025-05-24)</a></li>
<li><a class="reference internal" href="#section-40" id="toc-entry-43">16.0.1.0.9 (2025-05-23)</a></li>
<li><a class="reference internal" href="#section-41" id="toc-entry-44">16.0.1.0.8 (2025-05-21)</a></li>
<li><a class="reference internal" href="#section-42" id="toc-entry-45">16.0.1.0.7 (2025-05-16)</a></li>
<li><a class="reference internal" href="#section-43" id="toc-entry-46">16.0.1.0.6 (2025-05-16)</a></li>
<li><a class="reference internal" href="#section-44" id="toc-entry-47">16.0.1.0.5 (2025-05-09)</a></li>
<li><a class="reference internal" href="#section-45" id="toc-entry-48">16.0.1.0.4 (2025-04-30)</a></li>
<li><a class="reference internal" href="#section-46" id="toc-entry-49">16.0.1.0.3 (2025-04-22)</a></li>
<li><a class="reference internal" href="#section-47" id="toc-entry-50">16.0.1.0.2 (2025-04-22)</a></li>
<li><a class="reference internal" href="#section-48" id="toc-entry-51">16.0.1.0.1</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-52">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-53">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-54">Authors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-55">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.3.0.1 (2026-03-27)</a></h2>
<ul class="simple">
<li>Bugfixes: Waypoint behavior improvements. (5313)</li>
</ul>
</div>
<div class="section" id="section-2">
<h2><a class="toc-backref" href="#toc-entry-5">16.0.3.0.0 (2026-03-23)</a></h2>
<ul class="simple">
<li>Features: Jets! (4700)</li>
</ul>
</div>
<div class="section" id="section-3">
<h2><a class="toc-backref" href="#toc-entry-6">16.0.2.2.14 (2026-02-17)</a></h2>
<ul class="simple">
<li>Features: Blacklist filter for Python commands, value checker for
Vault. (5253)</li>
</ul>
</div>
<div class="section" id="section-4">
<h2><a class="toc-backref" href="#toc-entry-7">16.0.2.2.13 (2026-01-12)</a></h2>
<ul class="simple">
<li>Bugfixes: Last flight plan line post-run action was not triggered
correctly. (5120)</li>
</ul>
</div>
<div class="section" id="section-5">
<h2><a class="toc-backref" href="#toc-entry-8">16.0.2.2.12 (2026-01-11)</a></h2>
<ul class="simple">
<li>Features: Improve the File using template command flow, fix the
flight plan line view layout. (5197)</li>
</ul>
</div>
<div class="section" id="section-6">
<h2><a class="toc-backref" href="#toc-entry-9">16.0.2.2.11 (2026-01-08)</a></h2>
<ul class="simple">
<li>Bugfixes: Ensure custom values can be updated even if not provided
initially. (5175)</li>
</ul>
</div>
<div class="section" id="section-7">
<h2><a class="toc-backref" href="#toc-entry-10">16.0.2.2.10 (2026-01-08)</a></h2>
<ul class="simple">
<li>Features: Scheduled tasks: allow to select specific days of week.
(5190)</li>
</ul>
</div>
<div class="section" id="section-8">
<h2><a class="toc-backref" href="#toc-entry-11">16.0.2.2.8 (2025-12-22)</a></h2>
<ul class="simple">
<li>Bugfixes: Handle malformed expressions in flight plan line conditions.
(5154)</li>
</ul>
</div>
<div class="section" id="section-9">
<h2><a class="toc-backref" href="#toc-entry-12">16.0.2.2.7 (2025-12-16)</a></h2>
<ul class="simple">
<li>Features: Support for ANSI formatting in server logs. (5141)</li>
<li>Bugfixes: UI/UX fixed and improvements. (5141)</li>
</ul>
</div>
<div class="section" id="section-10">
<h2><a class="toc-backref" href="#toc-entry-13">16.0.2.2.6 (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-11">
<h2><a class="toc-backref" href="#toc-entry-14">16.0.2.2.5 (2025-12-10)</a></h2>
<ul class="simple">
<li>Bugfixes: Custom values in flight plan are lost in a skipped command
and are not available after it. (5129)</li>
</ul>
</div>
<div class="section" id="section-12">
<h2><a class="toc-backref" href="#toc-entry-15">16.0.2.2.4 (2025-12-10)</a></h2>
<ul class="simple">
<li>Features: Parse empty or missing key values as None instead of
leaving key reference as is. (5134)</li>
</ul>
</div>
<div class="section" id="section-13">
<h2><a class="toc-backref" href="#toc-entry-16">16.0.2.2.3 (2025-12-03)</a></h2>
<ul class="simple">
<li>Bugfixes: Save correct error message in log when SSH connection fails.
(5109)</li>
</ul>
</div>
<div class="section" id="section-14">
<h2><a class="toc-backref" href="#toc-entry-17">16.0.2.2.2 (2025-12-03)</a></h2>
<ul class="simple">
<li>Bugfixes: Make variables selectable in scheduled tasks (5105)</li>
</ul>
</div>
<div class="section" id="section-15">
<h2><a class="toc-backref" href="#toc-entry-18">16.0.2.2.0 (2025-11-12)</a></h2>
<ul class="simple">
<li>Features: Integrate user notifications into the main module, drop the
cetmix_tower_notify_backend module. (5074)</li>
</ul>
</div>
<div class="section" id="section-16">
<h2><a class="toc-backref" href="#toc-entry-19">16.0.2.0.6 (2025-10-27)</a></h2>
<ul class="simple">
<li>Features: Tag mixin and helper commands. (5039)</li>
</ul>
</div>
<div class="section" id="section-17">
<h2><a class="toc-backref" href="#toc-entry-20">16.0.2.0.5 (2025-10-16)</a></h2>
<ul class="simple">
<li>Bugfixes: Flight plan command exception handling (4930)</li>
</ul>
</div>
<div class="section" id="section-18">
<h2><a class="toc-backref" href="#toc-entry-21">16.0.2.0.4 (2025-10-13)</a></h2>
<ul class="simple">
<li>Features: Auto update references for related records (5005)</li>
</ul>
</div>
<div class="section" id="section-19">
<h2><a class="toc-backref" href="#toc-entry-22">16.0.2.0.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-20">
<h2><a class="toc-backref" href="#toc-entry-23">16.0.2.0.2 (2025-10-08)</a></h2>
<ul class="simple">
<li>Features: UI/UX improvements (4996)</li>
<li>Bugfixes: Handle secret values when a record is duplicated using
copy() (4996)</li>
</ul>
</div>
<div class="section" id="section-21">
<h2><a class="toc-backref" href="#toc-entry-24">16.0.2.0.1 (2025-10-08)</a></h2>
<ul class="simple">
<li>Bugfixes: Improve variable value references uniqueness (4961)</li>
</ul>
</div>
<div class="section" id="section-22">
<h2><a class="toc-backref" href="#toc-entry-25">16.0.2.0.0 (2025-10-07)</a></h2>
<ul class="simple">
<li>Features: Cetmix Tower Vault - new way of centralized password/key
management (4824)</li>
</ul>
</div>
<div class="section" id="section-23">
<h2><a class="toc-backref" href="#toc-entry-26">16.0.1.7.2 (2025-09-18)</a></h2>
<ul class="simple">
<li>Features: Set Auto Sync in files from file templates (4949)</li>
</ul>
</div>
<div class="section" id="section-24">
<h2><a class="toc-backref" href="#toc-entry-27">16.0.1.7.1 (2025-09-10)</a></h2>
<ul class="simple">
<li>Bugfixes: Check custom values in flight plan line condition (4922)</li>
</ul>
</div>
<div class="section" id="section-25">
<h2><a class="toc-backref" href="#toc-entry-28">16.0.1.6.4 (2025-08-18)</a></h2>
<ul class="simple">
<li>Features: Improve the extendability of the file upload command. (4759)</li>
</ul>
</div>
<div class="section" id="section-26">
<h2><a class="toc-backref" href="#toc-entry-29">16.0.1.6.3 (2025-08-13)</a></h2>
<ul class="simple">
<li>Features: Improve access settings for logs (4866)</li>
</ul>
</div>
<div class="section" id="section-27">
<h2><a class="toc-backref" href="#toc-entry-30">16.0.1.6.2 (2025-08-05)</a></h2>
<ul class="simple">
<li>Bugfixes: Pin paramiko version to “&lt;4” to maintain compatibility with
legacy installations (4891)</li>
</ul>
</div>
<div class="section" id="section-28">
<h2><a class="toc-backref" href="#toc-entry-31">16.0.1.6.0 (2025-07-30)</a></h2>
<ul class="simple">
<li>Features: Optional behaviour when file uploaded by command already
exists on the server. (4740)</li>
</ul>
</div>
<div class="section" id="section-29">
<h2><a class="toc-backref" href="#toc-entry-32">16.0.1.5.3 (2025-07-29)</a></h2>
<ul class="simple">
<li>Features: Make file references server dependent to be more unique
(4870)</li>
</ul>
</div>
<div class="section" id="section-30">
<h2><a class="toc-backref" href="#toc-entry-33">16.0.1.5.1 (2025-07-25)</a></h2>
<ul class="simple">
<li>Features: Select secrets from dropdown list in the code fields (4853)</li>
</ul>
</div>
<div class="section" id="section-31">
<h2><a class="toc-backref" href="#toc-entry-34">16.0.1.5.0 (2025-07-22)</a></h2>
<ul class="simple">
<li>Features: Select variables from dropdown list in the code fields
(4827)</li>
</ul>
</div>
<div class="section" id="section-32">
<h2><a class="toc-backref" href="#toc-entry-35">16.0.1.3.0 (2025-07-17)</a></h2>
<ul class="simple">
<li>Features: Add the tldextract and dnspython libraries. (4737)</li>
</ul>
</div>
<div class="section" id="section-33">
<h2><a class="toc-backref" href="#toc-entry-36">16.0.1.1.4 (2025-07-07)</a></h2>
<ul class="simple">
<li>Bugfixes: Command log sorting (4816)</li>
</ul>
</div>
<div class="section" id="section-34">
<h2><a class="toc-backref" href="#toc-entry-37">16.0.1.1.2 (2025-06-25)</a></h2>
<ul class="simple">
<li>Features: Required variables in servers (4779)</li>
</ul>
</div>
<div class="section" id="section-35">
<h2><a class="toc-backref" href="#toc-entry-38">16.0.1.1.1 (2025-06-21)</a></h2>
<ul class="simple">
<li>Features: Command view improvements (4753)</li>
</ul>
</div>
<div class="section" id="section-36">
<h2><a class="toc-backref" href="#toc-entry-39">16.0.1.1.0 (2025-06-20)</a></h2>
<ul class="simple">
<li>Features: Run commands and flight plans using scheduled tasks. (4650)</li>
</ul>
</div>
<div class="section" id="section-37">
<h2><a class="toc-backref" href="#toc-entry-40">16.0.1.0.12 (2025-06-06)</a></h2>
<ul class="simple">
<li>Features: Improve command and flight plan log management. (4749)</li>
</ul>
</div>
<div class="section" id="section-38">
<h2><a class="toc-backref" href="#toc-entry-41">16.0.1.0.11 (2025-06-06)</a></h2>
<ul class="simple">
<li>Bugfixes: Host key cannot be retrieved from the UI. (4747)</li>
</ul>
</div>
<div class="section" id="section-39">
<h2><a class="toc-backref" href="#toc-entry-42">16.0.1.0.10 (2025-05-24)</a></h2>
<ul class="simple">
<li>Features: Improve command log and flight plan form views (4697)</li>
</ul>
</div>
<div class="section" id="section-40">
<h2><a class="toc-backref" href="#toc-entry-43">16.0.1.0.9 (2025-05-23)</a></h2>
<ul class="simple">
<li>Bugfixes: Error when rendering a file not attached to a server. (4715)</li>
</ul>
</div>
<div class="section" id="section-41">
<h2><a class="toc-backref" href="#toc-entry-44">16.0.1.0.8 (2025-05-21)</a></h2>
<ul class="simple">
<li>Features: References for secret values. (4696)</li>
<li>Features: Make the “Host key” field non-required in the form view to
improve the UX. (4699)</li>
</ul>
</div>
<div class="section" id="section-42">
<h2><a class="toc-backref" href="#toc-entry-45">16.0.1.0.7 (2025-05-16)</a></h2>
<ul class="simple">
<li>Features: Option to preserve command splitting when using sudo. (4641)</li>
<li>Features: Record references for files. (4670)</li>
<li>Features: Use <tt class="docutils literal">sudo</tt> parameter to pass sudo mode to command runner
instead of using context. (4678)</li>
<li>Bugfixes: Incorrect sudo usage in commands run in wizard. Pass No
split for sudo property to commands run in wizard. (4679)</li>
</ul>
</div>
<div class="section" id="section-43">
<h2><a class="toc-backref" href="#toc-entry-46">16.0.1.0.6 (2025-05-16)</a></h2>
<ul class="simple">
<li>Features: Improve the key storage functionality. (4686)</li>
</ul>
</div>
<div class="section" id="section-44">
<h2><a class="toc-backref" href="#toc-entry-47">16.0.1.0.5 (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-45">
<h2><a class="toc-backref" href="#toc-entry-48">16.0.1.0.4 (2025-04-30)</a></h2>
<ul class="simple">
<li>Features: UI/UX improvements. (4642)</li>
</ul>
</div>
<div class="section" id="section-46">
<h2><a class="toc-backref" href="#toc-entry-49">16.0.1.0.3 (2025-04-22)</a></h2>
<ul class="simple">
<li>Features: Allow to pass custom variable values to commands (4524)</li>
<li>Features: Cetmix Tower Odoo Automation model: pass custom variable
values to the <tt class="docutils literal">server_run_command</tt> method. (4547)</li>
<li>Bugfixes: Random id generation, sudo command parsing, record rule
names, spelling errors in descriptions. (4612)</li>
</ul>
</div>
<div class="section" id="section-47">
<h2><a class="toc-backref" href="#toc-entry-50">16.0.1.0.2 (2025-04-22)</a></h2>
<ul class="simple">
<li>Bugfixes: Refactor secret value handling, fix the new server template
creation wizard. (4601)</li>
</ul>
</div>
<div class="section" id="section-48">
<h2><a class="toc-backref" href="#toc-entry-51">16.0.1.0.1</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-52">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%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-53">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-54">Authors</a></h2>
<ul class="simple">
<li>Cetmix</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-55">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">cetmix/cetmix-tower</a> project on GitHub.</p>
<p>You are welcome to contribute.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,507 +0,0 @@
/** @odoo-module **/
import {AceField} from "@web/views/fields/ace/ace_field";
import {AutocompletePopup} from "./autocomplete_popup.esm";
import {registry} from "@web/core/registry";
import {useService} from "@web/core/utils/hooks";
import {useState} from "@odoo/owl";
const POPUP_FALLBACK_WIDTH = 500;
const POPUP_FALLBACK_HEIGHT = 300;
class AceCommandField extends AceField {
/**
* Initialize the component with required services and properties
*/
setup() {
super.setup();
this.orm = useService("orm");
this.inputListener = null;
this.clickOutsideListener = null;
this.inputTimeout = null;
this.variables = [];
this.secrets = [];
// Use reactive state for properties that affect rendering
this.state = useState({
showPopup: false,
popupItems: [],
popupPosition: {},
selectedIndex: 0,
// Add popup type to distinguish between variables and secrets
popupType: "variables",
});
this.updateSelectedIndex = this.updateSelectedIndex.bind(this);
}
/**
* Load variables from the backend using ORM service
* @returns {Promise<void>}
*/
async loadVariables() {
try {
this.variables = await this.orm.searchRead(
"cx.tower.variable",
[],
["name", "reference"]
);
console.log(`Loaded ${this.variables.length} variables for autocomplete`);
} catch (error) {
console.error("Failed to load variables for autocomplete:", error);
this.variables = [];
this.env.services.notification.add(
"Failed to load autocomplete variables",
{type: "warning"}
);
}
}
/**
* Load secrets from the backend using ORM service
* @returns {Promise<void>}
*/
async loadSecrets() {
try {
this.secrets = await this.orm.searchRead(
"cx.tower.key",
[["key_type", "=", "s"]],
["name", "reference"]
);
console.log(`Loaded ${this.secrets.length} secrets for autocomplete`);
} catch (error) {
console.error("Failed to load secrets for autocomplete:", error);
this.secrets = [];
this.env.services.notification.add("Failed to load autocomplete secrets", {
type: "warning",
});
}
}
/**
* Set up ACE editor with custom autocompletion
*/
setupAce() {
super.setupAce();
if (this.aceEditor) {
this.setupCustomAutocompletion();
}
}
/**
* Configure custom autocompletion commands and keyboard bindings for ACE editor
*/
setupCustomAutocompletion() {
// Remove any existing conflicting commands first
this.aceEditor.commands.removeCommand("startAutocomplete");
this.aceEditor.commands.removeCommand("expandSnippet");
// Only add the main autocomplete trigger command
this.aceEditor.commands.addCommand({
name: "customAutoComplete",
bindKey: {win: "Ctrl-Space", mac: null},
exec: (editor) => {
this.showCustomCompletions(editor);
return true;
},
});
// Set up input listener for {{ and #! triggers
this.inputListener = () => {
// Clear any existing timeout
if (this.inputTimeout) {
clearTimeout(this.inputTimeout);
}
// Use setTimeout to ensure the text is fully processed
this.inputTimeout = setTimeout(() => {
const cursor = this.aceEditor.getCursorPosition();
const session = this.aceEditor.getSession();
const line = session.getLine(cursor.row);
const textBeforeCursor = line.substring(0, cursor.column);
// Check for variables trigger {{
if (textBeforeCursor.endsWith("{{")) {
// Remove {{ symbols from editor
const startColumn = Math.max(0, cursor.column - 2);
const range = {
start: {row: cursor.row, column: startColumn},
end: {row: cursor.row, column: cursor.column},
};
session.replace(range, "");
// Update cursor position
const newCursor = {
row: cursor.row,
column: startColumn,
};
this.aceEditor.moveCursorToPosition(newCursor);
this.showCustomCompletions(this.aceEditor, "variables");
}
// Check for secrets trigger !#
else if (textBeforeCursor.endsWith("#!")) {
// Remove !# symbols from editor
const startColumn = Math.max(0, cursor.column - 2);
const range = {
start: {row: cursor.row, column: startColumn},
end: {row: cursor.row, column: cursor.column},
};
session.replace(range, "");
// Update cursor position
const newCursor = {
row: cursor.row,
column: startColumn,
};
this.aceEditor.moveCursorToPosition(newCursor);
this.showCustomCompletions(this.aceEditor, "secrets");
}
}, 10);
};
this.aceEditor.on("input", this.inputListener);
}
/**
* Show custom completions popup with available variables or secrets
* @param {Object} editor - ACE editor instance
* @param {String} type - Type of completion ('variables' or 'secrets')
* @returns {Promise<void>}
*/
async showCustomCompletions(editor, type = "variables") {
const cursor = editor.getCursorPosition();
const session = editor.getSession();
const line = session.getLine(cursor.row);
const textBeforeCursor = line.substring(0, cursor.column);
let items = [];
let triggerLength = 0;
if (type === "secrets") {
// Handle secrets
await this.loadSecrets();
if (!this.secrets.length) {
return;
}
items = this.secrets;
} else {
// Handle variables
await this.loadVariables();
if (!this.variables.length) {
return;
}
items = this.variables;
// Check if we're already in a variable context
const isInVariableContext = textBeforeCursor.endsWith("{{");
if (isInVariableContext) {
triggerLength = 2;
}
}
const position = this.calculatePopupPosition(editor, cursor);
// Set popup type in state
this.state.popupType = type;
await this.showAutocompletePopup(items, position, editor, triggerLength, type);
}
/**
* Calculate the optimal position for the autocomplete popup
* @param {Object} editor - ACE editor instance
* @param {Object} cursor - Cursor position object
* @returns {Object} Position object with left and top coordinates
*/
calculatePopupPosition(editor, cursor) {
const renderer = editor.renderer;
// Calculate cursor position within the editor
const cursorPixelPos = renderer.textToScreenCoordinates(
cursor.row,
cursor.column
);
// Get scroll position
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
// Calculate the cursor position relative to the viewport
const viewportLeft = cursorPixelPos.pageX - scrollLeft;
const viewportTop = cursorPixelPos.pageY - scrollTop;
// Position popup just below the cursor
const finalLeft = viewportLeft;
const finalTop = viewportTop + renderer.lineHeight;
// Ensure popup doesn't go outside viewport
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const popup = document.querySelector(".ace-autocomplete-popup");
const popupWidth = popup ? popup.offsetWidth : POPUP_FALLBACK_WIDTH;
const popupHeight = popup ? popup.offsetHeight : POPUP_FALLBACK_HEIGHT;
let adjustedLeft = finalLeft;
let adjustedTop = finalTop;
// Adjust if popup would go off-screen horizontally
if (finalLeft + popupWidth > viewportWidth) {
adjustedLeft = finalLeft - popupWidth;
}
// Adjust if popup would go off-screen vertically
if (finalTop + popupHeight > viewportHeight) {
adjustedTop = finalTop - popupHeight - renderer.lineHeight;
}
// Make sure popup is not positioned off-screen
adjustedLeft = Math.max(0, adjustedLeft);
adjustedTop = Math.max(0, adjustedTop);
return {
left: adjustedLeft,
top: adjustedTop,
};
}
/**
* Display the autocomplete popup with variables or secrets at the specified position
* @param {Array} items - Array of available variables or secrets
* @param {Object} position - Position object with left and top coordinates
* @param {Object} editor - ACE editor instance
* @param {Number} triggerLength - Length of trigger text that should be replaced
* @param {String} type - Type of completion ('variables' or 'secrets')
* @returns {Promise<void>}
*/
async showAutocompletePopup(
items,
position,
editor,
triggerLength,
type = "variables"
) {
this.hideAutocompletePopup();
this.state.popupItems = items;
this.state.popupPosition = position;
this.state.showPopup = true;
this.state.selectedIndex = 0;
this.state.popupType = type;
this.currentEditor = editor;
this.currentTriggerLength = triggerLength;
this.currentType = type;
// Add click outside listener
this.clickOutsideListener = (event) => {
// Check if click is outside the popup and ace editor
const popupElement = document.querySelector(".ace-autocomplete-popup");
const aceElement = this.aceEditor.container;
if (
popupElement &&
!popupElement.contains(event.target) &&
aceElement &&
!aceElement.contains(event.target)
) {
this.hideAutocompletePopup();
}
};
setTimeout(() => {
document.addEventListener("click", this.clickOutsideListener, true);
}, 0);
}
/**
* Hide the autocomplete popup and clean up event listeners
*/
hideAutocompletePopup() {
// Remove click outside listener
if (this.clickOutsideListener) {
document.removeEventListener("click", this.clickOutsideListener, true);
this.clickOutsideListener = null;
}
this.state.showPopup = false;
this.state.popupVariables = [];
this.currentEditor = null;
this.state.selectedIndex = 0;
// Return focus to the ACE editor
if (this.aceEditor) {
this.aceEditor.focus();
}
}
/**
* Update the selected index in the autocomplete popup
* @param {Number} index - New selected index
*/
updateSelectedIndex(index) {
if (this.state) {
this.state.selectedIndex = index;
}
}
/**
* Handle selection of a command from the autocomplete popup
* @param {Object} command - Selected command object
* @param {Object} editor - ACE editor instance
*/
handleCommandSelection(command, editor) {
if (!command || !command.reference) {
this.hideAutocompletePopup();
return;
}
const cursor = editor.getCursorPosition();
const session = editor.getSession();
const line = session.getLine(cursor.row);
const textBeforeCursor = line.substring(0, cursor.column);
// Get line length for validation
const lineLength = session.getLine(cursor.row).length;
const currentType = this.currentType || this.state.popupType;
let range = null;
let insertText = "";
if (currentType === "secrets") {
// Handle secrets insertion
// Check if we're inside a secret context (between #!cxtower.secret and !#)
const lastSecretStart = textBeforeCursor.lastIndexOf("#!cxtower.secret");
const lastSecretEnd = textBeforeCursor.lastIndexOf("!#");
// Count occurrences of start and end delimiters for more robust validation
const startCount = (textBeforeCursor.match(/#!cxtower\.secret/g) || [])
.length;
const endCount = (textBeforeCursor.match(/!#/g) || []).length;
const isInsideSecret =
startCount > endCount &&
lastSecretStart > lastSecretEnd &&
lastSecretStart !== -1;
if (isInsideSecret) {
// We're inside a secret context, replace from after #!cxtower to cursor
range = {
start: {row: cursor.row, column: lastSecretStart + 16},
end: {row: cursor.row, column: cursor.column},
};
// Clamp range to valid bounds
range.start.column = Math.max(
0,
Math.min(range.start.column, lineLength)
);
range.end.column = Math.max(
range.start.column,
Math.min(range.end.column, lineLength)
);
insertText = `${command.reference}!#`;
} else {
// We're not in a secret context, insert complete secret
const triggerLength = this.currentTriggerLength || 0;
range = {
start: {row: cursor.row, column: cursor.column - triggerLength},
end: {row: cursor.row, column: cursor.column},
};
// Clamp range to valid bounds
range.start.column = Math.max(
0,
Math.min(range.start.column, lineLength)
);
range.end.column = Math.max(
range.start.column,
Math.min(range.end.column, lineLength)
);
insertText = `#!cxtower.secret.${command.reference}!#`;
}
} else {
// Handle variables insertion (existing logic)
const lastOpenBrace = textBeforeCursor.lastIndexOf("{{");
const lastCloseBrace = textBeforeCursor.lastIndexOf("}}");
const isInsideVariable =
lastOpenBrace > lastCloseBrace && lastOpenBrace !== -1;
if (isInsideVariable) {
// We're inside a variable context, replace from after {{ to cursor
range = {
start: {row: cursor.row, column: lastOpenBrace + 2},
end: {row: cursor.row, column: cursor.column},
};
// Clamp range to valid bounds
range.start.column = Math.max(
0,
Math.min(range.start.column, lineLength)
);
range.end.column = Math.max(
range.start.column,
Math.min(range.end.column, lineLength)
);
insertText = ` ${command.reference} `;
} else {
// We're not in a variable context, insert complete variable
const triggerLength = this.currentTriggerLength || 0;
range = {
start: {row: cursor.row, column: cursor.column - triggerLength},
end: {row: cursor.row, column: cursor.column},
};
// Clamp range to valid bounds
range.start.column = Math.max(
0,
Math.min(range.start.column, lineLength)
);
range.end.column = Math.max(
range.start.column,
Math.min(range.end.column, lineLength)
);
insertText = `{{ ${command.reference} }}`;
}
}
// Replace the text
session.replace(range, insertText);
// Get the updated line length after replacement
const updatedLineLength = session.getLine(cursor.row).length;
// Position cursor after the inserted text
const newCursor = {
row: cursor.row,
column: range.start.column + insertText.length,
};
newCursor.column = Math.max(0, Math.min(newCursor.column, updatedLineLength));
editor.moveCursorToPosition(newCursor);
this.hideAutocompletePopup();
editor.focus();
}
/**
* Clean up resources when component is destroyed
*/
destroy() {
if (this.inputTimeout) {
clearTimeout(this.inputTimeout);
}
if (this.aceEditor && this.inputListener) {
this.aceEditor.off("input", this.inputListener);
}
this.hideAutocompletePopup();
super.destroy();
}
}
AceCommandField.template = "cetmix_tower_server.AceCommandField";
AceCommandField.components = {
AutocompletePopup,
};
registry.category("fields").add("ace_tower", AceCommandField);
export {AceCommandField};

View File

@@ -1,44 +0,0 @@
// Custom styles ONLY for AceCommandField - more specific selectors
.o_field_widget.o_field_ace_tower {
display: block !important;
}
.o_field_widget[data-field-name] .o_field_ace.ace-command-field {
min-height: 200px !important;
height: auto !important;
width: 100% !important;
display: block !important;
.ace_editor {
min-height: 200px !important;
height: auto !important;
width: 100% !important;
border: 1px solid #ced4da;
border-radius: 4px;
display: block !important;
}
.ace_content {
min-height: 200px !important;
width: 100% !important;
}
.ace_scroller {
width: 100% !important;
// Remove any scroll restrictions that might affect standard ACE
overflow: auto !important;
}
}
// Custom autocomplete popup styles
.ace-autocomplete-popup {
.ace-autocomplete-item {
&:hover {
background-color: #e6f3ff !important;
}
&:last-child {
border-bottom: none !important;
}
}
}

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="cetmix_tower_server.AceCommandField" owl="1">
<t t-call="web.AceField" />
<t t-if="state.showPopup">
<AutocompletePopup
commands="state.popupItems"
position="state.popupPosition"
selectedIndex="state.selectedIndex"
type="state.popupType"
onSelectedIndexChange="updateSelectedIndex"
onItemClick="(command) => this.handleCommandSelection(command, this.currentEditor)"
/>
</t>
</t>
</templates>

View File

@@ -1,317 +0,0 @@
/** @odoo-module **/
import {Component, useEffect, useRef, useState} from "@odoo/owl";
class AutocompletePopup extends Component {
/**
* Component setup method that initializes refs, state, and effects
*/
setup() {
this.popupRef = useRef("popupRef");
this.searchInput = useRef("searchInput");
this.itemsContainer = useRef("itemsContainer");
// State for search functionality
this.state = useState({
searchTerm: "",
});
useEffect(
() => {
this.scrollToSelected();
},
() => [this.props.selectedIndex]
);
// Auto-focus search input when popup opens
useEffect(
() => {
if (this.searchInput.el) {
// Use setTimeout to ensure DOM is ready
const timeoutId = setTimeout(() => {
this.searchInput.el.focus();
}, 0);
return () => clearTimeout(timeoutId);
}
},
() => []
);
useEffect(
() => {
if (this.props.position) {
const timeoutId = setTimeout(() => {
if (this.popupRef.el) {
this.popupRef.el.style.left = `${this.props.position.left}px`;
this.popupRef.el.style.top = `${this.props.position.top}px`;
this.popupRef.el.style.position = "fixed";
}
}, 0);
return () => clearTimeout(timeoutId);
}
},
() => [this.props.position]
);
// Cleanup effect to clear search timeout
useEffect(
() => {
return () => {
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
};
},
() => []
);
}
/**
* Updates search term from external keyboard input (from editor)
* @param {String} char - The character typed or 'Backspace' for deletion
*/
updateSearchFromEditor(char) {
if (char === "Backspace") {
this.state.searchTerm = this.state.searchTerm.slice(0, -1);
} else if (char.length === 1) {
this.state.searchTerm += char;
}
if (this.props.onSelectedIndexChange) {
this.props.onSelectedIndexChange(0);
}
}
/**
* Filters commands based on search term with enhanced search capabilities
* @returns {Array} Filtered and sorted array of commands matching the search term
*/
get filteredCommands() {
if (!this.state.searchTerm.trim()) {
return this.props.commands;
}
const searchTerm = this.state.searchTerm.toLowerCase();
// Filter and score commands based on search relevance
const scoredCommands = this.props.commands
.map((command) => {
const name = (command.name || "").toLowerCase();
const reference = (command.reference || "").toLowerCase();
let score = 0;
// Exact matches get highest priority
if (name === searchTerm || reference === searchTerm) {
score = 1000;
}
// Starts with search term gets high priority
else if (
name.startsWith(searchTerm) ||
reference.startsWith(searchTerm)
) {
score = 100;
}
// Contains search term gets medium priority
else if (name.includes(searchTerm) || reference.includes(searchTerm)) {
score = 10;
}
// No match
else {
return null;
}
// Boost score for name matches over reference matches
if (name.includes(searchTerm)) {
score += 5;
}
// Boost score for shorter matches (more relevant)
score += Math.max(0, 50 - Math.min(name.length, reference.length));
return {command, score};
})
.filter((item) => item !== null)
.sort((a, b) => b.score - a.score)
.map((item) => item.command);
return scoredCommands;
}
/**
* Debounces the search filtering
* @param {String} searchTerm - The search term to set
*/
debouncedSearch(searchTerm) {
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
this.searchTimeout = setTimeout(() => {
this.state.searchTerm = searchTerm;
if (this.props.onSelectedIndexChange) {
this.props.onSelectedIndexChange(0);
}
}, 150);
}
/**
* Handles search input changes
* @param {Event} ev - The input event
*/
onSearchInput(ev) {
ev.stopPropagation();
this.debouncedSearch(ev.target.value);
}
/**
* Common keyboard navigation logic
* @param {KeyboardEvent} ev - The keyboard event
*/
handleKeyboardNavigation(ev) {
if (ev.key === "ArrowDown") {
ev.preventDefault();
const newIndex = Math.min(
(this.props.selectedIndex || 0) + 1,
this.filteredCommands.length - 1
);
if (this.props.onSelectedIndexChange) {
this.props.onSelectedIndexChange(newIndex);
}
this.scrollToSelected();
} else if (ev.key === "ArrowUp") {
ev.preventDefault();
const newIndex = Math.max((this.props.selectedIndex || 0) - 1, 0);
if (this.props.onSelectedIndexChange) {
this.props.onSelectedIndexChange(newIndex);
}
this.scrollToSelected();
} else if (ev.key === "Enter") {
ev.preventDefault();
const selectedCommand =
this.filteredCommands[this.props.selectedIndex || 0];
if (selectedCommand) {
this.onItemClick(selectedCommand);
}
} else if (ev.key === "Escape") {
ev.preventDefault();
this.props.onItemClick(null);
}
}
/**
* Handles keydown events on search input
* @param {KeyboardEvent} ev - The keyboard event
*/
onSearchKeyDown(ev) {
ev.stopPropagation();
this.handleKeyboardNavigation(ev);
}
/**
* Handles focus events on search input
* @param {FocusEvent} ev - The focus event
*/
onSearchFocus(ev) {
ev.stopPropagation();
}
/**
* Handles blur events on search input
* @param {FocusEvent} ev - The blur event
*/
onSearchBlur(ev) {
ev.stopPropagation();
}
/**
* Handles click events on search input
* @param {MouseEvent} ev - The click event
*/
onSearchClick(ev) {
ev.stopPropagation();
}
/**
* Handles mousedown events on search input
* @param {MouseEvent} ev - The mousedown event
*/
onSearchMouseDown(ev) {
ev.stopPropagation();
}
/**
* Handles item click events
* @param {Object} command - The selected command object
*/
onItemClick(command) {
this.props.onItemClick(command);
}
/**
* Handles close button click events
*/
onCloseClick() {
this.props.onItemClick(null);
}
/**
* Handles global keydown events for the popup
* @param {KeyboardEvent} ev - The keyboard event
*/
onKeyDown(ev) {
// Handle search input from editor keyboard events
if (ev.key.length === 1 && ev.key.match(/[a-zA-Z0-9_]/)) {
// Add typed character to search
this.updateSearchFromEditor(ev.key);
} else if (ev.key === "Backspace") {
// Remove last character from search
this.updateSearchFromEditor("Backspace");
} else {
// Use common keyboard navigation logic
this.handleKeyboardNavigation(ev);
}
}
/**
* Scrolls the selected item into view
*/
scrollToSelected() {
const itemsContainer = this.itemsContainer.el;
if (
itemsContainer &&
this.props.selectedIndex !== undefined &&
this.props.selectedIndex >= 0 &&
this.props.selectedIndex < itemsContainer.children.length
) {
const selectedItem = itemsContainer.children[this.props.selectedIndex];
if (selectedItem) {
selectedItem.scrollIntoView({
block: "nearest",
behavior: "smooth",
});
}
}
}
/**
* Returns CSS class for autocomplete item based on selection state
* @param {Number} index - The item index
* @returns {String} CSS class string
*/
getItemClass(index) {
return index === (this.props.selectedIndex || 0)
? "ace-autocomplete-item ace-autocomplete-item-selected"
: "ace-autocomplete-item";
}
}
AutocompletePopup.template = "cetmix_tower_server.AutocompletePopup";
AutocompletePopup.props = {
commands: {type: Array},
onItemClick: {type: Function},
position: {type: Object},
selectedIndex: {type: Number, optional: true},
onSelectedIndexChange: {type: Function, optional: true},
type: {type: String, optional: true},
};
export {AutocompletePopup};

View File

@@ -1,190 +0,0 @@
// Define z-index variable for better management
$z-index-autocomplete: 1050; // Above dropdowns but below modals
.ace-autocomplete-popup {
position: absolute; // Keep original positioning for cursor placement
background: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
z-index: $z-index-autocomplete;
min-width: 300px;
max-width: 500px;
max-height: 300px;
overflow: hidden;
font-family: monospace;
font-size: 14px;
// Mobile adaptations
@media (max-width: 768px) {
width: 90%;
min-width: unset;
max-width: unset;
left: 50% !important;
transform: translateX(-50%);
font-size: 13px;
}
}
.ace-autocomplete-search {
padding: 8px;
padding-right: 48px; // Add right padding to avoid overlap with close button
border-bottom: 1px solid #eee;
background: #f8f9fa;
// Mobile: reduce padding
@media (max-width: 768px) {
padding: 6px;
padding-right: 56px;
}
}
.ace-autocomplete-search-input {
width: 100%;
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 13px;
outline: none;
box-sizing: border-box;
}
.ace-autocomplete-search-input:focus {
border-color: #007cba;
box-shadow: 0 0 0 2px rgba(0, 124, 186, 0.1);
}
.ace-autocomplete-items {
max-height: 240px;
overflow-y: auto;
/* Standard scrollbar styling (Firefox 64+) */
scrollbar-width: thin;
scrollbar-color: #c1c1c1 #f1f1f1;
}
/* Scrollbar styling for webkit browsers */
.ace-autocomplete-items::-webkit-scrollbar {
width: 6px;
}
.ace-autocomplete-items::-webkit-scrollbar-track {
background: #f1f1f1;
}
.ace-autocomplete-items::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.ace-autocomplete-items::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
.ace-autocomplete-item {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
// Mobile: stack items vertically with reduced padding
@media (max-width: 768px) {
flex-direction: column;
align-items: flex-start;
padding: 6px 12px;
}
}
.ace-autocomplete-item:hover {
background-color: #f5f5f5;
}
.ace-autocomplete-item-selected {
background-color: #e6f3ff;
color: #0066cc;
}
.ace-autocomplete-item-selected:hover {
background-color: #cce7ff;
}
.command-name {
font-weight: 600;
color: #333;
flex: 1;
margin-right: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
// Mobile adaptations
@media (max-width: 768px) {
margin-right: 0;
margin-bottom: 2px; // Reduced from 4px
width: 100%;
font-size: 14px;
}
}
.command-description {
color: #666;
font-size: 12px;
font-style: italic;
flex-shrink: 0;
// Mobile adaptations
@media (max-width: 768px) {
font-size: 11px;
width: 100%;
word-break: break-word;
}
}
.ace-autocomplete-no-results {
padding: 16px 12px;
text-align: center;
color: #999;
font-style: italic;
}
// Close button styles
.ace-autocomplete-close-btn {
position: absolute;
top: 8px;
right: 8px;
background: none;
border: none;
font-size: 20px;
font-weight: bold;
color: #666;
cursor: pointer;
padding: 4px 8px;
line-height: 1;
border-radius: 3px;
z-index: 1;
min-width: 30px;
min-height: 30px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background-color: #f0f0f0;
color: #333;
}
&:active {
background-color: #e0e0e0;
}
// Mobile-friendly touch target
@media (max-width: 768px) {
min-width: 40px;
min-height: 40px;
font-size: 24px;
top: 4px;
right: 4px;
}
}

View File

@@ -1,75 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="cetmix_tower_server.AutocompletePopup" owl="1">
<div
class="ace-autocomplete-popup"
t-ref="popupRef"
tabindex="0"
role="combobox"
t-att-aria-label="props.type === 'secrets' ? 'Secret search' : 'Variable search'"
aria-expanded="true"
aria-haspopup="listbox"
t-att-aria-owns="props.type === 'secrets' ? 'secrets-list' : 'variables-list'"
>
<!-- Close button for mobile convenience -->
<button
class="ace-autocomplete-close-btn"
t-on-click="onCloseClick"
type="button"
title="Close"
t-att-aria-label="props.type === 'secrets' ? 'Close secret search' : 'Close variable search'"
>
×
</button>
<!-- Search input field -->
<div class="ace-autocomplete-search">
<input
type="text"
class="ace-autocomplete-search-input"
t-att-placeholder="props.type === 'secrets' ? 'Search secrets...' : 'Search variables...'"
t-model="state.searchTerm"
t-ref="searchInput"
t-on-input="onSearchInput"
t-on-keydown="onSearchKeyDown"
t-on-focus="onSearchFocus"
t-on-blur="onSearchBlur"
t-att-aria-label="props.type === 'secrets' ? 'Search secrets' : 'Search variables'"
t-att-aria-controls="props.type === 'secrets' ? 'secrets-list' : 'variables-list'"
aria-autocomplete="list"
t-att-aria-activedescendant="`${props.type === 'secrets' ? 'sec' : 'var'}-opt-${props.selectedIndex}`"
/>
</div>
<!-- Items list -->
<div
class="ace-autocomplete-items"
t-ref="itemsContainer"
t-att-id="props.type === 'secrets' ? 'secrets-list' : 'variables-list'"
role="listbox"
>
<div
t-foreach="filteredCommands"
t-as="command"
t-key="command.name"
t-att-id="`${props.type === 'secrets' ? 'sec' : 'var'}-opt-${command_index}`"
t-att-class="getItemClass(command_index)"
t-on-click="() => this.onItemClick(command)"
role="option"
t-att-aria-selected="command_index === props.selectedIndex ? 'true' : 'false'"
>
<span class="command-name" t-esc="command.name" />
<span
class="command-description"
t-esc="props.type === 'secrets' ? `${command.reference}` : `{{ ${command.reference} }}`"
/>
</div>
<div
t-if="!filteredCommands.length"
class="ace-autocomplete-no-results"
>
<t t-if="props.type === 'secrets'">No secrets found</t>
<t t-else="">No variables found</t>
</div>
</div>
</div>
</t>
</templates>

View File

@@ -1,33 +0,0 @@
/** @odoo-module */
import {registry} from "@web/core/registry";
import {StateSelectionField} from "@web/views/fields/state_selection/state_selection_field";
import {STATUS_COLORS, STATUS_COLOR_PREFIX} from "../../utils/server_utils.esm";
export class ServerStatusField extends StateSelectionField {
/**
* @override
*/
setup() {
super.setup();
this.colorPrefix = STATUS_COLOR_PREFIX;
this.colors = STATUS_COLORS;
}
/**
* @override
*/
get options() {
return [[false, "Undefined"], ...super.options];
}
/**
* @override
*/
get showLabel() {
return !this.props.hideLabel;
}
}
registry.category("fields").add("server_status", ServerStatusField);

View File

@@ -1,33 +0,0 @@
.o_server_status_bubble {
@extend .o_status;
&.o_color_server_status_bubble_info {
background-color: $o-info;
}
&.o_color_server_status_bubble_success {
background-color: $o-success;
}
&.o_color_server_status_bubble_danger {
background-color: $o-danger;
}
&.o_color_server_status_bubble_warning {
background-color: $o-warning;
}
}
.o_field_server_status {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 8px;
margin: 0px 16px;
border-radius: 5px;
border: 1px solid #e5e5e5;
width: fit-content !important;
.o_status_label {
color: #4c4c4c;
font-size: 14px;
margin-left: 0.5rem !important;
display: block;
}
}

View File

@@ -1,17 +0,0 @@
/** @odoo-module */
/**
* List of colors according to the selection value
*/
export const STATUS_COLORS = {
false: "info",
stopped: "danger",
starting: "warning",
running: "success",
stopping: "warning",
restarting: "warning",
delete_error: "danger",
};
export const STATUS_COLOR_PREFIX =
"o_server_status_bubble mx-0 o_color_server_status_bubble_";