Tower: upload havari_license_client 16.0.1.0.0 (was 16.0.1.0.0, via marketplace)

This commit is contained in:
2026-05-19 11:43:17 +00:00
parent 1f83918b72
commit e6c70307da
22 changed files with 282 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
# Pyarmor 9.2.3 (basic), 009742, subscription_packages, 2026-03-02T02:53:41.853123
from .pyarmor_runtime_009742 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY009742\x00\x03\t\x00a\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\xad\x01\x00\x00\x12\t\x05\x00\xe1\xf72\x8c\xc6\xe7\xf4"\x82_\xf2\x91\xb6\x88\x94\x87\x00\x00\x00\x00\x00\x00\x00\x00\x91\x94\x13\x9c\x9d8r\xbc\xf64\x1a\x97^q\x92\xc7\xc0\xd4B=9\xc4\xf6hI\x90\x7f\n\x08\x1b\xd1\xb6\'.\n$\xf7 b\xde\xb5\xb0\xf5h\x00U\x11\xcfp\xca\xdaN\xd7\xa13z\xbe\xb0\x0f7\xaf\x8bn\xeb\xf0\xf8\x9f#>i\x08\x8ei\xd7\xd6\xe6\xd8\xb8\xe9\x81\xa5\x8b-\x1dt\xd8JJ\xec!\xce\xe8&\xdc\x9a\x11P\x84\xcf\x0f\xe5\x8dn >$,\x05\xb6b\x07l\x82\x1fIL\xa5\xdbh\x16\x0f\xe3]\xeb6\xca\xd9\xf1\x92\xa8\xfb- \x98\xd3\x80#\x92W\x1cNeN\x82>5\xa4\x17\x01\x8b\xfeL\xd8\x90\x9c\r\xe21C\x13\x08$\x87\xa9l\x1f\xb4\x0f\xe0\xda[\xf8\x9b.P\x97\xeb\xd8\xdb\xa4u\x92\x03\x82\xe0\xf4 7\xc0\xe8\xfbE7\x132\xa8\x91\xa5\x84\xbf2\xbb)7\x105j\x0b\\X\xb7\x9c\xd5\xd6\x81\xb1\x9fP\xb1fz0\xd0\x1ft\xce\xb6\x90_\xf5%\xf9\xc7\xadU\xcf\xb5\xe6ha<\x81\xee\xef\xa9\xa1\xeb\xa1L\x11\xf0[\xcb\xf8\xffW\xb3\xfbAY\x14\xcc\x03x\x0br`Y\xa6WM\x1e\xfdq\xc1W{8@0\x04|\x1e\x08\x88\x1e\xdf7F\x82\x01\x1fNn\xa6\x0c\x8c\xe5\xcf\xb6\xbf\xc1$g\xb73kX\x85\xa7\xa1\xc9M\xc2t\xcb\x14E\x08\'=\xeb=\xe4\x0b\x91\xe0\x01\xd1P\x10\xa5\x95\xf9y\xa9\x0f1\x17\xa6z|\x8a~}\x16\x1b\x13\xf1~\xd6\xc8b\xbf\xbe\xf50\xf1\xbf>b\x8e\x9d\xe2\xaf\xedW5\x0e\x02\x13\xd0\x15\xd9\x06\xbet\xa5i\x9e#\xad\x05HQO\xa5Z8%\xc0\x1a?\xb0@>G\x81O\xf4&\x9f\x1d\x1e\xa4f\xff\xe3m\x1a\xcf\xa3\xfe\ng8\x02~\xc7\xe9\xff\x04\x93\xd2,a2\x8f\xe8')

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
{
'name': 'License Client',
'version': '16.0.1.0.0',
'summary': 'License Management',
'category': 'Tools',
'author': 'Mostafa Elhavari',
'website': 'https://havari.me',
'maintainer': 'Mostafa Elhavari <m@havari.me>',
'support': 'm@havari.me',
'license': 'LGPL-3',
# Developer Contact: +90 543 774 3103 (WhatsApp)
'depends': ['base', 'web', 'bus'],
'data': [
'security/ir.model.access.csv',
'views/license_product_views.xml',
'views/res_config_settings_views.xml',
],
'assets': {
'web.assets_backend': [
'havari_license_client/static/src/js/license_bus.js',
],
},
'installable': True,
'auto_install': False,
'application': False,
}

View File

@@ -0,0 +1,3 @@
# Pyarmor 9.2.3 (basic), 009742, subscription_packages, 2026-03-02T02:53:42.098901
from ..pyarmor_runtime_009742 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY009742\x00\x03\t\x00a\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00~\x01\x00\x00\x12\t\x05\x00^\xcc\xa9\xbb\xeb/UmM\x0b\x11<\xebv\x01v\x00\x00\x00\x00\x00\x00\x00\x00\xbf\xa3|\xcdel\x8d\xda&N\xce}j\xfe.\xe2*\xfb\xc0\xbb\x16t\x12\x87\x80\xc2\xff\r\x89\x1e\x826\x85L\xd9\x9a}L\xd4\xf6\xba4\xd7\xa0\x96\xdb\x9d\x12\xac\x05\xa6f\x11H\x99m\xed\xfd\xbah\x08\xee\x9e\x83\xf0\x8e\r\xa9\x98\x901T\xb7Y\xf8\xffi-\x85\x87\t\x16\x01L\xdb\xc1\x9e:\xbc\n\xc4\xdbu\xcf7k\xa1T*29*"Y\xafS\xeaF\x06\xaf;t\xb0\xf93p\x13g\xcd\xfe!X\x97\xa3(D\xf5\x06\xad\xd4G\xa7t\x02\x9f\xfb\xb9\xd0\xb5(\xb6Y,+\x80wki9<\xaeV\x97\xd0a\x9c\xc4\xa1\xe9k\x08\x1b\x18\xfb\xd7\x95\xea\xda\x8eZ\xd6_\xc4k\xd7X\xe3\xd2\x06\x82\xe0zg\x0b\x97\x11\xbak\xfa:\xf0@\x18K\\w\xf5\xc0+sX>w\x83Z\xe8R\xf0d\x12\n,s\xd6\xa0\xf5\xfc\xb5\x9b\x96\xe0\x8f\xbf\x19\xc6\n\x7f\x05S\x17\x81x\xc6\'\x04^\xe6T\xbfu\x8cMlv\x9f\xb1D\x8e\x86\xf9\xd4\xc0,\x92,\xcee$\x9b\xb9\xa4\xe5!\x17\x85\x80,\x15o#u\xed+\xc1/\x1c\x83\x1dv\x9d\xb9`\xed\xcf\xc42OB!\x95P\x82`h\xc5-t\t^\xe3dzC\xb1U\xc4\x9f\xc6M\x1c0z\xe5\xc43\xe2K$\x12]\x06\xf5\xf0&\xab$1t\xda\x07^\xd8\xc8\xe7\x8c}\x82\x86\x94\xf3\xa5\x08\xc6\x06\xe4\xb6Z%Ha\x03\x10\xa1\x85\xb1\x11\xae\xd1ZW\x87\x82\xc2\x8c\xe4\xca\xef\x1c\x0f\xe5\xa4\xc6\x8d\xcf\xe2\x93GSn)\x7fd')

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
# Pyarmor 9.2.3 (basic), 009742, subscription_packages, 2026-03-02T02:53:42.053885
from ..pyarmor_runtime_009742 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY009742\x00\x03\t\x00a\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\xb1\x01\x00\x00\x12\t\x05\x00\xc4\xdf\xe9\x92\x064\x0e5\xb9\xd1\xe0e\x03l\xddt\x00\x00\x00\x00\x00\x00\x00\x00\xe7\x82\xc6\xda\x91(\xeeN\x01+\xdf;\x00\x99J"\xf3D\x9aeNF\x03\xeaoJ\xaa\xec$PP\xbf@\xf1\xd4\xb38\x17\xd5\xdb\x88z5\xd3\x1e\xe7\xdb\xb6\xde\xe7\x03nK\x91\xd6Y\x07y\xbcO\xf6y\xd7H\x15\xe4\x8b\x03NpA\xe8\xdchs\x13\xb4\xbak\x02\x8eNJ\'\xc1W/JAx\x14\xc4m\xad\x061\xd0E\xab\xe6M\x98\xcc\x9d\x15\xe7\x13\x1f\x16T\x02\'\x9d\xd9\xfe\x18<\x86=A;4\xb8#\x1bf\'\xfd\xea\xc6\x8b\xde\x17\xde\x96\tE\xef\xca\xb1\xb2F\x83\x97\xfam@\x9e\x98\x17\xbe5\xfb9"\xc4\xbd\xe8\xfb\x8d\xc9\xbd+\xef\x8b\xc7\xa7\x11dm#\x88\xb9\xe6\x7f\xc3\xea\xc2\x87\x02SWU\x87\x03zh\xd3\xd9\xe3\xfd\xfa\x8e\xa5\xae\xb3~\x92\x8f\x95\xe6\xcc\x9c\xb8\x85\x10\xc3\x93\xe7\x836\x84=\xac\xa1\x18\xa2=\xae\x81\xe4\xaf\xba\xe7ZV\xeblE.\xaa\x82\x94n\xd3\xd0\xf9\xcey\xf8\x9f\x98\xaf\xbd\xc5l>\xd7A\x93%?<;\xd8\x07g`\xec<_\xa1\x9f\x8c\x0b` \xf7\x1c\xb0\xf3\x06\xdb`q\x87\x0e_\xc8\x18\x01\x00f$8\xfc\x854i)^k\x82k\'\xdf?Z\xd3\x14\\\xc7\xff,\x8b\xbe;\xaa\x0ek\n\xa0\xf4Y!9\x1f\xe9R\x7f\x84\xbf\xc0@2S\x9e\xe1\x91\xc9g\\\xd9,\xfd\xd6\xf3m92=\x7f\xe6L\xb8\xb9\xffy\xb4\x9c\x8ba\xf2;Q\x8eV\x95\x15\x9c3\x1dX\xa5\x94d\x0f~\r\xa4\xdc\xa2\x08\xf0a\x13M\xa7\xf6{\x1d\x9e\xcb\x13\x85\xf65u\xcbaM\xef5M\xef\xc00+\x8f\xca\x81\xef\xa9\x8a\xbfc\x0f\xe8Uk\x18su0\xb2\xb48N>v\nG\xc4\xcb\x97\xbf^\x00O\xec/$\x1a')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
# Pyarmor 9.2.3 (basic), 009742, subscription_packages, 2026-03-02T02:53:41.865151
from ..pyarmor_runtime_009742 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY009742\x00\x03\t\x00a\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\x19\x02\x00\x00\x12\t\x05\x00\xe7\x86\xd0\xf5\x94\xf2\xe9\xed\xbe\xd2&\xe2k\xb9\x11\\\x00\x00\x00\x00\x00\x00\x00\x00~\xdb\xf1\xf0\xc1l}\xeddG\xd9<\xc0}\xe4\x7f\xe3D\xee\xda\x07\xf3\x8d3\xd3\xeelLd[\xc3\xd0qv@\xbd\xc3\x8e|O\xf2\xb2\x9fL\xa9\x9f\xba\x8dN\x11\x1b\xfe\x1c9\x00\x87\tk\n*\xc6\xd7\xa9HT\xa4\x1d\xe7\xbc\xa9\xbe\xb4\x03(W\xd8\xc9\xea#\xc7\x17V\t\xf0J_\xba>\xb3\xe7\xf2\x9f\xf5\x12k\xf5\x8f\xa0\x1dm2\xc3\x81\x17\xba( \xf5O\xee\xc3\xe4n4\x80/\xa63pt:\xb2\xacO\xaa\na"m<W]\x8d\xb9\xf2\xdbT,\x10\x8d\xb4h.4L;\xbb8\xe4a\x99_.*\xd6\xac\x8d_#\xc4d\xbd\x8f\x0b`\xb8\xc3-\x05>%\xe0\xd1\x88Fm\xe2\x0bG%\xdb\x15\x87\xd2 )2\xdb3L\x8e\xfcP\xcf\xd6(B\xd5\x06\xfb3r\x9e\xd05\xe5\xfe\xb4\xd4\xfd\xf5\x92\xd9K0\xa8^\xd9\x88\xe1$\x02\xce6\xdd\xb00\xc8\x10&\xab\x06sA=\xe1\x10u25\xd9A\xbe\x91NM\xdc\xb8\xd6\xa4FE^\xfb\x1e\x10\xe3\xfb|I\x04%{\xec1n\x05\x08xn/BS\x84\xc7\x05\xcd00\x02\xc9\x82\xd4\xacF\xbfC6Q\xd0d>\xa9k\xbf\x11ep>R\xeb\x13z\xe1_\xd9\xb9|k\xad(\xa4\x1f\xc0te\xa9\xe72\x95\xebCC\xc5\x82?\xf5`\x9e\x813\xdd\x8e\x9f\x95\xdet\x9e\x1f\x1e\xb91>\xe4\x04i\xa4\xf4%\xdc\x1a\x89>7-z.]\xb2;\x91r\x8bs\x8bm#\x9bD\xdc\x969U\x1cT\x90\xc0E\xdfF=j\xc9\x11"\xd0/\\\xe9G\xdf\xbby\xa7\t\xeb\x1e\r\x9c\xd2l\x8f\x8bH\xc8*\x0c\xe3\xcf\xc8\x1a\x86\xeb\xd4P\xb4+\x818\x12\xee\x97\xe9pt\xa6r\rbf\xc2\xfa\xf8\xbf^H\xe2\xa3\x89\x01\xac\x16\xfe\x9d\r\xbfS2b\x97s|\x05u\x1c\xb5\x85#[\xd9%\xf83s\xf1\nN\xa6Cf\xbcf<\xf1G\x8e\xfdD\xe3{\xf4\xcb\xd9\t\x06\xd3\x9c\x9b;c\xe8)y\x08\x1c\xe6F\xfd\x00\xa0<\x0e?\x0b\xb1C\xab!\x7f\x16#sK\xc5\xe1\xecg\xfe\x87/(\xd1\x9c\x8cM\x9e4\xa9\xd3,1\xa5\x17\x03_\xe61')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
# Pyarmor 9.2.3 (basic), 009742, 2026-03-02T02:53:41.840189
from .pyarmor_runtime import __pyarmor__

View File

@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_license_product_admin,license.product.admin,model_license_product,base.group_system,1,1,1,1
access_license_product_user,license.product.user,model_license_product,base.group_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_license_product_admin license.product.admin model_license_product base.group_system 1 1 1 1
3 access_license_product_user license.product.user model_license_product base.group_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1e3c72"/>
<stop offset="100%" style="stop-color:#2a5298"/>
</linearGradient>
<linearGradient id="shieldGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#4facfe"/>
<stop offset="100%" style="stop-color:#00f2fe"/>
</linearGradient>
</defs>
<!-- Background rounded square -->
<rect x="8" y="8" width="240" height="240" rx="48" ry="48" fill="url(#bgGrad)"/>
<!-- Shield shape -->
<path d="M128 45
L195 70
L195 130
Q195 175 128 210
Q61 175 61 130
L61 70
Z"
fill="url(#shieldGrad)" opacity="0.9"/>
<!-- Checkmark inside shield -->
<path d="M100 130 L120 150 L160 100"
fill="none" stroke="white" stroke-width="12" stroke-linecap="round" stroke-linejoin="round"/>
<!-- Small key icon at bottom -->
<g transform="translate(128, 185)">
<circle cx="0" cy="0" r="8" fill="white" opacity="0.8"/>
<rect x="-2" y="5" width="4" height="15" rx="2" fill="white" opacity="0.8"/>
<rect x="0" y="12" width="8" height="3" rx="1" fill="white" opacity="0.8"/>
</g>
<!-- Decorative dots -->
<circle cx="50" cy="50" r="4" fill="white" opacity="0.3"/>
<circle cx="206" cy="50" r="4" fill="white" opacity="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,45 @@
/** @odoo-module **/
import { browser } from "@web/core/browser/browser";
import { registry } from "@web/core/registry";
/**
* License Bus Service
* Listens for license status updates from the server and reloads the page when needed.
*/
export const licenseBusService = {
dependencies: ["bus_service", "notification"],
start(env, { bus_service, notification }) {
// Subscribe to license status updates
bus_service.subscribe("license/status_update", (payload) => {
console.log("License status update received:", payload);
const { module_name, action, state, message, requires_reload } = payload;
// Show notification to user
if (action === "suspend" || action === "revoke") {
notification.add(message || `License for ${module_name} has been ${state}`, {
title: "License Status Changed",
type: "danger",
sticky: true,
});
// Reload page after 3 seconds to apply changes
if (requires_reload) {
setTimeout(() => {
browser.location.reload();
}, 3000);
}
} else if (action === "activate") {
notification.add(message || `License for ${module_name} has been activated`, {
title: "License Activated",
type: "success",
sticky: false,
});
}
});
},
};
registry.category("services").add("license_bus", licenseBusService);

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- License Product Tree View -->
<record id="view_license_product_tree" model="ir.ui.view">
<field name="name">license.product.tree</field>
<field name="model">license.product</field>
<field name="arch" type="xml">
<tree string="Licensed Modules" create="false" edit="false" delete="false"
decoration-success="state == 'active'"
decoration-info="state == 'trial'"
decoration-warning="state in ('grace_period', 'pending')"
decoration-danger="state in ('expired', 'suspended', 'revoked')">
<field name="display_name"/>
<field name="technical_name"/>
<field name="state" widget="badge"
decoration-success="state == 'active'"
decoration-info="state == 'trial'"
decoration-warning="state in ('grace_period', 'pending')"
decoration-danger="state in ('expired', 'suspended', 'revoked')"/>
<field name="expires"/>
<field name="days_remaining"/>
<field name="last_check"/>
<button name="action_check_license" type="object" string="Check" icon="fa-refresh" class="btn-link"/>
</tree>
</field>
</record>
<!-- License Product Form View - Read Only (Client Safe) -->
<record id="view_license_product_form" model="ir.ui.view">
<field name="name">license.product.form</field>
<field name="model">license.product</field>
<field name="arch" type="xml">
<form string="License Details" create="false" edit="false" delete="false">
<header>
<button name="action_check_license" type="object" string="Check License" class="btn-primary"/>
<field name="state" widget="statusbar" statusbar_visible="pending,trial,active,expired"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="display_name" readonly="1"/>
</h1>
</div>
<group>
<group string="License Status">
<field name="expires" readonly="1" string="Expiry Date"/>
<field name="days_remaining" readonly="1"/>
<field name="message" readonly="1" string="Status"/>
</group>
<group string="Support">
<field name="purchase_url" widget="url" readonly="1" string="Renew License"/>
<field name="contact_url" widget="url" readonly="1" string="Contact Support"/>
</group>
</group>
<!-- Hidden sensitive fields -->
<group invisible="1">
<field name="technical_name"/>
<field name="name"/>
<field name="license_key"/>
<field name="fingerprint"/>
<field name="last_check"/>
<field name="last_successful_check"/>
<field name="allow_offline"/>
<field name="offline_days"/>
<field name="force_online"/>
<field name="in_grace_period"/>
<field name="grace_period_days_remaining"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- License Product Action -->
<record id="action_license_product" model="ir.actions.act_window">
<field name="name">Licensed Modules</field>
<field name="res_model">license.product</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No licensed modules installed yet
</p>
<p>
Licensed modules will appear here automatically when installed.
</p>
</field>
</record>
<!-- Menu Item (under Settings) -->
<menuitem id="menu_license_product"
name="Licensed Modules"
parent="base.menu_administration"
action="action_license_product"
sequence="100"/>
</odoo>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Add License section to General Settings -->
<record id="res_config_settings_view_form_license" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.license</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="priority">100</field>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<app data-string="Licenses" string="Licenses" name="havari_license_client" groups="base.group_system">
<block title="Licensed Modules" name="license_block">
<setting string="Module Licenses" help="View and manage licenses for installed modules">
<div class="content-group">
<div class="mt16">
<field name="license_products_summary" readonly="1" nolabel="1"/>
</div>
<div class="mt16">
<button name="action_check_all_licenses" type="object"
string="Check All Licenses" class="btn-secondary" icon="fa-refresh"/>
<button name="action_view_licenses" type="object"
string="View All Licenses" class="btn-link" icon="fa-list"/>
</div>
</div>
</setting>
</block>
</app>
</xpath>
</field>
</record>
</odoo>