mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 21:52:00 +02:00
vanilla 19.0
This commit is contained in:
parent
991d2234ca
commit
d1963a3c3a
3066 changed files with 1651266 additions and 922560 deletions
|
|
@ -12,48 +12,48 @@
|
|||
<field name="external_report_layout_id" invisible="1" />
|
||||
<field name="logo_primary_color" invisible="1" />
|
||||
<field name="logo_secondary_color" invisible="1" />
|
||||
<field name="report_layout_id" widget="radio" string="Layout" required="1"/>
|
||||
<field name="font" widget="selection" required="1"/>
|
||||
<field name="logo" widget="image" options="{'size': [0, 50]}"/>
|
||||
<label for="primary_color" string="Colors" />
|
||||
<div class="o_document_layout_colors">
|
||||
<field name="primary_color" widget="color"/>
|
||||
<field name="secondary_color" widget="color"/>
|
||||
<div class="o_custom_colors" title="Reset to logo colors"
|
||||
attrs="{'invisible': [('custom_colors', '=', False)]}">
|
||||
<span class="fa fa-refresh fa-2x"></span>
|
||||
<field name="custom_colors" nolabel="1"/>
|
||||
</div>
|
||||
|
||||
<field name="report_layout_id" string="Layout" widget="selection_badge" required="1" options="{'horizontal': true, 'size': 'sm'}"/>
|
||||
<field name="layout_background" string="Background" widget="selection" required="1"/>
|
||||
<field name="layout_background_image" options="{'accepted_file_extensions': 'image/*'}" invisible="layout_background != 'Custom'" required="layout_background == 'Custom'">Upload your file</field>
|
||||
<field name="font" string="Text" widget="selection" required="1"/>
|
||||
<field name="logo" string="Logo" widget="image" options="{'size': [0, 50]}"/>
|
||||
|
||||
<label for="primary_color" string="Colors"/>
|
||||
<div class="o_document_layout_colors d-flex align-items-end mb-4">
|
||||
<field name="primary_color" widget="color" class="w-auto m-0 me-1"/>
|
||||
<field name="secondary_color" widget="color" class="w-auto m-0"/>
|
||||
<a class="o_custom_colors btn btn-secondary btn-sm position-relative ms-2" role="button" title="Reset to logo colors" invisible="not custom_colors">
|
||||
<i class="fa fa-repeat"/> Reset
|
||||
<field name="custom_colors" class="position-absolute top-0 start-0 w-100 h-100 opacity-0" nolabel="1"/>
|
||||
</a>
|
||||
</div>
|
||||
<field name="layout_background" widget="selection" required="1"/>
|
||||
<field name="layout_background_image" options="{'accepted_file_extensions': 'image/*'}" attrs="{'invisible': [('layout_background', '!=', 'Custom')], 'required': [('layout_background', '=', 'Custom')]}">Upload your file</field>
|
||||
<field name="report_header" placeholder="e.g. Global Business Solutions" options="{'resizable': false}"/>
|
||||
<field name="company_details" string="Company Details" options="{'resizable': false}"/>
|
||||
<field name="report_footer" string="Footer" options="{'resizable': false}"/>
|
||||
<field name="paperformat_id" widget="selection" required="1" domain="[('report_ids', '=', False)]"/>
|
||||
|
||||
<field name="company_details" string="Address" options="{'resizable': false}"/>
|
||||
<field name="report_header" string="Tagline" placeholder="e.g. Global Business Solutions" options="{'resizable': false}"/>
|
||||
<field name="report_footer" placeholder="Write your phone, email, bank account, tax ID, ..." string="Footer" options="{'resizable': false}"/>
|
||||
<field name="paperformat_id" widget="selection" required="1"/>
|
||||
</group>
|
||||
<div>
|
||||
<field name="preview" widget="iframe_wrapper" />
|
||||
<button name="web.action_report_layout_preview" string="Download PDF Preview" type="action" class="oe_link" icon="fa-arrow-right"/>
|
||||
<div class="o_preview">
|
||||
<field name="preview" widget="iframe_wrapper" class="preview_document_layout d-flex justify-content-center mb-0"/>
|
||||
</div>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Save" class="btn-primary" type="object" name="document_layout_save" data-hotkey="q"/>
|
||||
<button special="cancel" data-hotkey="z" string="Cancel" />
|
||||
<button string="Continue" class="btn-primary" type="object" name="document_layout_save" data-hotkey="q"/>
|
||||
<button special="cancel" data-hotkey="x" string="Discard" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_base_document_layout_configurator" model="ir.actions.act_window">
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="name">Configure your document layout</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="res_model">base.document.layout</field>
|
||||
<field name="view_id" ref="web.view_base_document_layout"/>
|
||||
<field name="context">{"dialog_size": "extra-large"}</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
|
|
|
|||
22
odoo-bringout-oca-ocb-web/web/views/ir_ui_view_views.xml
Normal file
22
odoo-bringout-oca-ocb-web/web/views/ir_ui_view_views.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_view_form_inherit_view" model="ir.ui.view">
|
||||
<field name="name">ir.ui.view.form.inherit</field>
|
||||
<field name="model">ir.ui.view</field>
|
||||
<field name="inherit_id" ref="base.view_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('alert-info')]" position="after">
|
||||
<div class="alert alert-danger" role="alert" invisible="not invalid_locators">
|
||||
Please note that your view includes invalid locators.<br/>
|
||||
These nodes could not be anchored to the parent view and have no effect.<br/>
|
||||
This issue may have arisen as a result of manual modifications or during the upgrade process.<br/>
|
||||
For your reference, invalid xpath nodes are highlighted in red.
|
||||
</div>
|
||||
<field name="invalid_locators" invisible="1"/> <!-- required for the alert -->
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='arch_base']" position="attributes">
|
||||
<attribute name="widget">code_ir_ui_view</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="assets_backend_legacy_lazy" name="Lazy assets for legacy Views" groups="base.group_user">
|
||||
<t t-call-assets="web.assets_backend_legacy_lazy" />
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
393
odoo-bringout-oca-ocb-web/web/views/memory_template.xml
Normal file
393
odoo-bringout-oca-ocb-web/web/views/memory_template.xml
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="view_memory">
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Memory Usage Visualization</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
|
||||
<style>
|
||||
/* General body and layout styles */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Row definitions for the new layout */
|
||||
.top-row {
|
||||
height: 40vh; /* Line graph takes 40% of the viewport height */
|
||||
}
|
||||
.bottom-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
/* The height of this row will be determined by its content */
|
||||
}
|
||||
|
||||
/* Chart container styles */
|
||||
.chart-container {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Specific styling for the bottom row elements */
|
||||
.bar-chart-wrapper {
|
||||
flex: 2; /* Bar chart takes 2/3 of the width */
|
||||
min-height: 400px; /* Minimum height for the bar chart */
|
||||
}
|
||||
.traceback-panel {
|
||||
flex: 1; /* Traceback panel takes 1/3 of the width */
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
overflow-y: auto;
|
||||
min-height: 400px; /* Match min-height for alignment */
|
||||
height: fit-content; /* Adjust height to content */
|
||||
}
|
||||
|
||||
/* Traceback panel content styling */
|
||||
.traceback-header {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.traceback-item {
|
||||
background: white;
|
||||
margin: 5px 0;
|
||||
padding: 8px;
|
||||
border-radius: 3px;
|
||||
border-left: 3px solid #007acc;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
.traceback-file {
|
||||
color: #007acc;
|
||||
font-weight: bold;
|
||||
}
|
||||
.copyable {
|
||||
cursor: pointer;
|
||||
user-select: all;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.copyable:hover {
|
||||
background-color: #e8f4f8;
|
||||
}
|
||||
.traceback-line {
|
||||
color: #666;
|
||||
}
|
||||
.no-selection {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
padding-top: 50px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Top row for the full-width line chart -->
|
||||
<div class="top-row">
|
||||
<div class="chart-container">
|
||||
<canvas id="totalMemoryChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bottom row for the bar chart and traceback panel -->
|
||||
<div class="bottom-row">
|
||||
<div class="bar-chart-wrapper chart-container" id="memoryChartContainer">
|
||||
<canvas id="memoryChart"></canvas>
|
||||
</div>
|
||||
<div class="traceback-panel" id="tracebackPanel">
|
||||
<div class="no-selection">Click a point on the top chart, then a bar below to see details.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Get data from Odoo template
|
||||
const rawData = JSON.parse(atob('<t t-esc="memory_graph"/>'));
|
||||
|
||||
// --- UTILITY FUNCTIONS ---
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k));
|
||||
const value = bytes / Math.pow(k, i);
|
||||
return `${value.toFixed(1)} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp) {
|
||||
const date = new Date(timestamp * 1000);
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
function getLastFileFromTraceback(traceback) {
|
||||
if (!traceback || traceback.length === 0) return "unknown:0";
|
||||
const lastFrame = traceback[traceback.length - 1];
|
||||
return `${lastFrame[0]}:${lastFrame[1]}`;
|
||||
}
|
||||
|
||||
function getFullTracebackKey(traceback) {
|
||||
if (!traceback || traceback.length === 0) return "unknown";
|
||||
return traceback.map(frame => `${frame[0]}:${frame[1]}`).join(' -> ');
|
||||
}
|
||||
|
||||
// --- DYNAMIC UI UPDATES ---
|
||||
function updateChartHeight(chart, dataLength) {
|
||||
const container = document.getElementById('memoryChartContainer');
|
||||
if (dataLength === 0) {
|
||||
container.style.height = '400px'; // Reset to min-height
|
||||
chart.resize();
|
||||
return;
|
||||
}
|
||||
|
||||
const minBarHeight = 35; // Minimum height per bar for readability
|
||||
const padding = 100; // Extra space for axes and labels
|
||||
const newHeight = Math.max(400, (dataLength * minBarHeight) + padding);
|
||||
|
||||
container.style.height = newHeight + 'px';
|
||||
chart.resize();
|
||||
}
|
||||
|
||||
function showTraceback(traceback, size, fullTracebackKey) {
|
||||
const panel = document.getElementById('tracebackPanel');
|
||||
if (!traceback || traceback.length === 0) {
|
||||
panel.innerHTML = '<div class="no-selection">No traceback available</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `<div class="traceback-header">Traceback (${formatBytes(size)})</div>`;
|
||||
|
||||
if (fullTracebackKey) {
|
||||
html += `
|
||||
<div style="margin-bottom: 10px; padding: 8px; background: #e8f4f8; border-radius: 3px; font-size: 11px; color: #555;">
|
||||
<strong>Full Path:</strong> <span class="copyable" title="Click to select for copying">${fullTracebackKey}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
traceback.forEach(frame => {
|
||||
const [filename, lineNo, functionName, code] = frame;
|
||||
html += `
|
||||
<div class="traceback-item">
|
||||
<div class="traceback-file copyable" title="Click to select for copying">${filename}:${lineNo}</div>
|
||||
<div>in <strong>${functionName || 'unknown'}</strong></div>
|
||||
${code ? `<div class="traceback-line">${code}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
panel.innerHTML = html;
|
||||
}
|
||||
|
||||
// --- CHART LOGIC ---
|
||||
const totalSizes = rawData.map(entry =>
|
||||
entry.samples.reduce((sum, sample) => sum + sample.size, 0)
|
||||
);
|
||||
const lineLabels = rawData.map(entry => formatTimestamp(entry.start));
|
||||
|
||||
let selectedIndexes = [];
|
||||
let currentBreakdownData = [];
|
||||
|
||||
const updateBarChart = () => {
|
||||
if (selectedIndexes.length === 1) {
|
||||
const entry = rawData[selectedIndexes[0]];
|
||||
const groupedData = {};
|
||||
entry.samples.forEach(sample => {
|
||||
const key = getLastFileFromTraceback(sample.traceback);
|
||||
if (!groupedData[key]) {
|
||||
groupedData[key] = { size: 0, traceback: sample.traceback, samples: [] };
|
||||
}
|
||||
groupedData[key].size += sample.size;
|
||||
groupedData[key].samples.push(sample);
|
||||
});
|
||||
|
||||
currentBreakdownData = Object.entries(groupedData).map(([key, data]) => ({
|
||||
label: key,
|
||||
size: data.size,
|
||||
traceback: data.traceback,
|
||||
samples: data.samples
|
||||
})).sort((a, b) => b.size - a.size);
|
||||
|
||||
breakdownChart.data.labels = currentBreakdownData.map(item => item.label);
|
||||
breakdownChart.data.datasets = [{
|
||||
label: `Memory Usage`,
|
||||
data: currentBreakdownData.map(item => item.size),
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
borderWidth: 1
|
||||
}];
|
||||
breakdownChart.options.plugins.title.text = `Memory Breakdown for ${formatTimestamp(entry.start)}`;
|
||||
|
||||
} else if (selectedIndexes.length === 2) {
|
||||
const [idx1, idx2] = selectedIndexes.sort((a, b) => a - b);
|
||||
const entry1 = rawData[idx1];
|
||||
const entry2 = rawData[idx2];
|
||||
|
||||
const groupDataByFullTraceback = (entry) => {
|
||||
const grouped = {};
|
||||
entry.samples.forEach(sample => {
|
||||
const key = getFullTracebackKey(sample.traceback);
|
||||
if (!grouped[key]) {
|
||||
grouped[key] = { size: 0, traceback: sample.traceback, lastFile: getLastFileFromTraceback(sample.traceback) };
|
||||
}
|
||||
grouped[key].size += sample.size;
|
||||
});
|
||||
return grouped;
|
||||
};
|
||||
|
||||
const data1 = groupDataByFullTraceback(entry1);
|
||||
const data2 = groupDataByFullTraceback(entry2);
|
||||
const labelSet = new Set([...Object.keys(data1), ...Object.keys(data2)]);
|
||||
|
||||
let diffData = [];
|
||||
labelSet.forEach(fullTracebackKey => {
|
||||
const size1 = data1[fullTracebackKey]?.size || 0;
|
||||
const size2 = data2[fullTracebackKey]?.size || 0;
|
||||
const diff = size2 - size1;
|
||||
if (diff !== 0) {
|
||||
const item = data2[fullTracebackKey] || data1[fullTracebackKey];
|
||||
diffData.push({
|
||||
label: item.lastFile,
|
||||
diff: diff,
|
||||
traceback: item.traceback,
|
||||
fullTracebackKey: fullTracebackKey
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
currentBreakdownData = diffData.sort((a, b) => b.diff - a.diff);
|
||||
|
||||
breakdownChart.data.labels = currentBreakdownData.map(item => item.label);
|
||||
breakdownChart.data.datasets = [{
|
||||
label: `Difference`,
|
||||
data: currentBreakdownData.map(item => item.diff),
|
||||
backgroundColor: item => item.raw >= 0 ? 'rgba(75, 192, 192, 0.6)' : 'rgba(255, 99, 132, 0.6)',
|
||||
borderColor: item => item.raw >= 0 ? 'rgba(75, 192, 192, 1)' : 'rgba(255, 99, 132, 1)',
|
||||
borderWidth: 1
|
||||
}];
|
||||
breakdownChart.options.plugins.title.text = `Memory Difference: ${formatTimestamp(entry2.start)} vs ${formatTimestamp(entry1.start)}`;
|
||||
|
||||
} else {
|
||||
currentBreakdownData = [];
|
||||
breakdownChart.data.labels = [];
|
||||
breakdownChart.data.datasets = [];
|
||||
breakdownChart.options.plugins.title.text = 'Memory Breakdown';
|
||||
document.getElementById('tracebackPanel').innerHTML =
|
||||
'<div class="no-selection">Click a point on the top chart, then a bar below to see details.</div>';
|
||||
}
|
||||
|
||||
updateChartHeight(breakdownChart, currentBreakdownData.length);
|
||||
breakdownChart.update();
|
||||
totalChart.update();
|
||||
};
|
||||
|
||||
// --- CHART INSTANTIATION ---
|
||||
const totalChart = new Chart(document.getElementById('totalMemoryChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: lineLabels,
|
||||
datasets: [{
|
||||
label: 'Total Memory Usage',
|
||||
data: totalSizes,
|
||||
fill: false,
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
tension: 0.1,
|
||||
pointBackgroundColor: ctx => selectedIndexes.includes(ctx.dataIndex) ? 'rgba(255, 99, 132, 1)' : 'rgba(75, 192, 192, 1)',
|
||||
pointRadius: ctx => selectedIndexes.includes(ctx.dataIndex) ? 7 : 4,
|
||||
pointBorderWidth: ctx => selectedIndexes.includes(ctx.dataIndex) ? 3 : 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: { display: true, text: 'Total Memory Usage Over Time (Click to select, Shift+Click to compare)' },
|
||||
tooltip: { callbacks: { label: context => `Total: ${formatBytes(context.raw)}` } }
|
||||
},
|
||||
onClick: (evt, elements) => {
|
||||
if (elements.length > 0) {
|
||||
const idx = elements[0].index;
|
||||
const isSelected = selectedIndexes.includes(idx);
|
||||
if (evt.native.shiftKey) {
|
||||
if (isSelected) {
|
||||
selectedIndexes = selectedIndexes.filter(i => i !== idx);
|
||||
} else {
|
||||
selectedIndexes.push(idx);
|
||||
if (selectedIndexes.length > 2) selectedIndexes.shift();
|
||||
}
|
||||
} else {
|
||||
selectedIndexes = isSelected && selectedIndexes.length === 1 ? [] : [idx];
|
||||
}
|
||||
} else {
|
||||
selectedIndexes = [];
|
||||
}
|
||||
updateBarChart();
|
||||
},
|
||||
scales: {
|
||||
y: { beginAtZero: true, ticks: { callback: formatBytes }, title: { display: true, text: 'Total Memory' } },
|
||||
x: { title: { display: true, text: 'Time' } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const breakdownChart = new Chart(document.getElementById('memoryChart'), {
|
||||
type: 'bar',
|
||||
data: { labels: [], datasets: [] },
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
layout: { padding: { right: 120 } },
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: true, text: 'Memory Breakdown' },
|
||||
tooltip: { callbacks: { label: context => `${formatBytes(context.raw)}` } },
|
||||
datalabels: {
|
||||
anchor: 'end',
|
||||
align: 'right',
|
||||
color: '#000',
|
||||
font: { weight: 'bold', size: 11 },
|
||||
formatter: (value) => formatBytes(value),
|
||||
display: true,
|
||||
clip: false
|
||||
}
|
||||
},
|
||||
onClick: (evt, elements) => {
|
||||
if (elements.length > 0 && currentBreakdownData.length > 0) {
|
||||
const item = currentBreakdownData[elements[0].index];
|
||||
const fullKey = item.fullTracebackKey || getFullTracebackKey(item.traceback);
|
||||
showTraceback(item.traceback, Math.abs(item.diff || item.size), fullKey);
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: { beginAtZero: false, ticks: { callback: formatBytes }, title: { display: true, text: 'Memory Usage / Difference' } },
|
||||
y: { title: { display: true, text: 'File:Line' }, ticks: { maxRotation: 0, font: { size: 11 } } }
|
||||
}
|
||||
},
|
||||
plugins: [ChartDataLabels]
|
||||
});
|
||||
|
||||
// Initial render
|
||||
updateBarChart();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
</odoo>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
display: block;
|
||||
font-size: 16px;
|
||||
{{ neutralize_banner_style or '' }}">
|
||||
<t t-out="neutralize_banner_text">This database is neutralized.</t>
|
||||
<t t-out="neutralize_banner_text">Database neutralized for testing: no emails sent, etc.</t>
|
||||
</span>
|
||||
</div>
|
||||
</xpath>
|
||||
|
|
|
|||
17
odoo-bringout-oca-ocb-web/web/views/partner_view.xml
Normal file
17
odoo-bringout-oca-ocb-web/web/views/partner_view.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record model="ir.actions.server" id="download_contact">
|
||||
<field name="name">Download (vCard)</field>
|
||||
<field name="model_id" ref="model_res_partner"/>
|
||||
<field name="binding_model_id" ref="model_res_partner"/>
|
||||
<field name="binding_view_types">form,list,kanban</field>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
action = {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/web/partner/vcard?partner_ids=' + ','.join(map(str, records.ids)),
|
||||
'target': 'download',
|
||||
}
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,96 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="config_speedscope_index">
|
||||
<link rel="stylesheet" href="/web/assets/any/web.assets_web.min.css" />
|
||||
<script src="/web/assets/any/web.assets_web.min.js" type="text/javascript"/>
|
||||
<div class="container-md p-0">
|
||||
<div class="o_form_view align-items-center">
|
||||
<div class="row">
|
||||
<div class="col" t-if="profiles._compute_has_memory()">
|
||||
<div class="o_form_sheet">
|
||||
<form method="get" t-attf-action="/web/profile_config/{{profile_str}}">
|
||||
<h4>Memory profile</h4>
|
||||
<div class="mt-3">
|
||||
<label for="memory_limit">Memory limit (in bytes)</label>
|
||||
<input type="number" id="memory_limit" name="memory_limit" class="form-control" value="1000"/>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="profile_id" t-att-value="profile_str" />
|
||||
|
||||
<div class="mt-3">
|
||||
<button type="submit" class="btn btn-primary" name="action" value="memory_open">open</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="o_form_sheet">
|
||||
<!-- Single Form -->
|
||||
<form method="get" t-attf-action="/web/speedscope/{{profile_str}}">
|
||||
<h4>Speedscope</h4>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="constant_time" name="constant_time" value="True" />
|
||||
<label class="form-check-label" for="constant_time">Constant Time</label>
|
||||
</div>
|
||||
<!-- add more variable here -->
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="aggregate_sql" name="aggregate_sql" value="True" />
|
||||
<label class="form-check-label" for="aggregate_sql">Aggregate SQL</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="use_execution_context" name="use_execution_context" value="True" checked="1"/>
|
||||
<label class="form-check-label" for="use_execution_context">Use execution context</label>
|
||||
</div>
|
||||
<div t-if="len(profiles) > 1">
|
||||
<hr/>
|
||||
<h4>Multiple profile</h4>
|
||||
<label class="form-check-label" for="profile_aggregation_mode">Aggregation mode</label>
|
||||
<select class="form-select" name="profile_aggregation_mode" id="profile_aggregation_mode">
|
||||
<option value="tabs">Separated (one per tab)</option>
|
||||
<option value="temporal">Temporal (experimental)</option>
|
||||
</select>
|
||||
<span id="temporal_warning" class="alert alert-warning" style="display:none"><b>Warning:</b> Temporal mode will merge all samples.<br/>It can lead to partially invalid result in case of concurrent profiles.<br/>Use with caution </span>
|
||||
<script>
|
||||
document.getElementById('profile_aggregation_mode').addEventListener('change', function (event) {
|
||||
document.getElementById('temporal_warning').style.display = event.target.value === 'temporal'? 'block': 'none';
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<hr/>
|
||||
<h4>Display presets</h4>
|
||||
<t t-set="has_sql" t-value="any(profile.sql for profile in profiles)"/>
|
||||
<t t-set="has_traces" t-value="any(profile.traces_async for profile in profiles)"/>
|
||||
<div class="form-check" t-if="has_sql and has_traces">
|
||||
<input type="checkbox" class="form-check-input" id="combined_profile" name="combined_profile" value="True" t-att-checked="default_params.get('combined_profile')"/>
|
||||
<label class="form-check-label" for="combined_profile">Frames + sql (Combined)</label>
|
||||
</div>
|
||||
<div class="form-check" t-if="has_sql">
|
||||
<input type="checkbox" class="form-check-input" id="sql_no_gap_profile" name="sql_no_gap_profile" value="True" t-att-checked="default_params.get('sql_no_gap_profile')"/>
|
||||
<label class="form-check-label" for="sql_no_gap_profile">SQL no gap</label>
|
||||
</div>
|
||||
<div class="form-check" t-if="has_sql">
|
||||
<input type="checkbox" class="form-check-input" id="sql_density_profile" name="sql_density_profile" value="True" t-att-checked="default_params.get('sql_density_profile')"/>
|
||||
<label class="form-check-label" for="sql_density_profile">SQL density</label>
|
||||
</div>
|
||||
<div class="form-check" t-if="has_traces">
|
||||
<input type="checkbox" class="form-check-input" id="frames_profile" name="frames_profile" value="True" t-att-checked="default_params.get('frames_profile')"/>
|
||||
<label class="form-check-label" for="frames_profile">Frames</label>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="profile_id" t-att-value="profile_str" />
|
||||
|
||||
<div class="mt-3">
|
||||
<button type="submit" class="btn btn-secondary" name="action" value="speedscope_download_json"><i class="fa fa-download"/> (json)</button>
|
||||
<button type="submit" class="btn btn-secondary" name="action" value="speedscope_download_html"><i class="fa fa-download"/> (html)</button>
|
||||
<button type="submit" class="btn btn-primary" name="action" value="speedscope_open">Open</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</odoo>
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Speedscope for odoo</title>
|
||||
<script t-if="profile">window.location.hash="#profileURL=<t t-esc="url_root"/>web/content/ir.profile/<t t-esc="profile.id"/>/speedscope"</script>
|
||||
<script>window.location.hash="#localProfilePath=1"</script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet"/>
|
||||
<link rel="stylesheet"
|
||||
t-attf-href="{{cdn}}reset.8c46b7a1.css"
|
||||
|
|
@ -20,6 +20,12 @@
|
|||
crossorigin="anonymous"
|
||||
integrity="sha256-CvDqAOMjq0Sv/D59O5JSbzzXoClZ3rptt6ts8D/6CWw="
|
||||
></script>
|
||||
<script>
|
||||
const b64 = '<t t-esc="speedscope_base64"/>';
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
speedscope.loadFileFromBase64('Profile', b64);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,19 +3,27 @@
|
|||
|
||||
<!-- Call this template instead of "web.assets_tests" to have the proper conditional check -->
|
||||
<template id="conditional_assets_tests" name="Tests Assets Bundle">
|
||||
<t t-call-assets="web.assets_tests" t-if="'tests' in debug or test_mode_enabled" defer_load="True" />
|
||||
<t t-if="'tests' in debug or test_mode_enabled">
|
||||
<t t-if="ignore_missing_deps">
|
||||
<!-- FIXME: This is only to ignore the errors for the lazy loading. To allow all tests assets and tours to be in the same bundle, the assets_tests bundle ignores missing dependencies -->
|
||||
<t t-call-assets="web.__assets_tests_call__" defer_load="True" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call-assets="web.assets_tests" defer_load="True" />
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="web.layout" name="Web layout"><!DOCTYPE html>
|
||||
<html t-att="html_data or {}">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<title t-esc="title or 'Odoo'"/>
|
||||
<link type="image/x-icon" rel="shortcut icon" t-att-href="x_icon or '/web/static/img/favicon.ico'"/>
|
||||
<script id="web.layout.odooscript" type="text/javascript">
|
||||
var odoo = {
|
||||
csrf_token: "<t t-nocache="The csrf token must always be up to date." t-esc="request.csrf_token(None)"/>",
|
||||
csrf_token: "<t t-esc="request.csrf_token(None)"/>",
|
||||
debug: "<t t-esc="debug"/>",
|
||||
};
|
||||
</script>
|
||||
|
|
@ -36,7 +44,7 @@
|
|||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
</xpath>
|
||||
<xpath expr="//head/script[@id='web.layout.odooscript']" position="after">
|
||||
<script t-nocache="Session information should always be up to date." type="text/javascript">
|
||||
<script type="text/javascript">
|
||||
odoo.__session_info__ = <t t-out="json.dumps(request.env['ir.http'].get_frontend_session_info())"/>;
|
||||
if (!/(^|;\s)tz=/.test(document.cookie)) {
|
||||
const userTZ = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
|
@ -44,7 +52,7 @@
|
|||
}
|
||||
</script>
|
||||
<t t-call-assets="web.assets_frontend_minimal" t-css="false" defer_load="True"/>
|
||||
<t t-call="web.conditional_assets_tests"/>
|
||||
<t t-call="web.conditional_assets_tests" ignore_missing_deps="True"/>
|
||||
<t t-call-assets="web.assets_frontend_lazy" t-css="false" lazy_load="True"/>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-out='0']" position="replace">
|
||||
|
|
@ -57,11 +65,11 @@
|
|||
<main>
|
||||
<t t-out="0"/>
|
||||
</main>
|
||||
<footer t-cache="no_footer,no_copyright" t-if="not no_footer" id="bottom" data-anchor="true" t-attf-class="bg-light o_footer">
|
||||
<footer t-if="not no_footer" id="bottom" data-anchor="true" t-attf-class="bg-light o_footer">
|
||||
<div id="footer"/>
|
||||
<div t-if="not no_copyright" class="o_footer_copyright">
|
||||
<div class="container py-3">
|
||||
<div class="row">
|
||||
<div class="row row-gap-2">
|
||||
<div class="col-sm text-center text-sm-start text-muted">
|
||||
<span class="o_footer_copyright_name me-2">Copyright &copy; <span t-field="res_company.name" itemprop="name">Company name</span></span>
|
||||
</div>
|
||||
|
|
@ -109,7 +117,7 @@
|
|||
<div class="container py-5">
|
||||
<div t-attf-class="card border-0 mx-auto bg-100 {{login_card_classes}} o_database_list" style="max-width: 300px;">
|
||||
<div class="card-body">
|
||||
<div t-attf-class="text-center pb-3 border-bottom {{'mb-3' if form_small else 'mb-4'}}">
|
||||
<div class="text-center pb-3 border-bottom mb-4">
|
||||
<img t-attf-src="/web/binary/company_logo{{ '?dbname='+db if db else '' }}" alt="Logo" style="max-height:120px; max-width: 100%; width:auto"/>
|
||||
</div>
|
||||
<t t-out="0"/>
|
||||
|
|
@ -127,25 +135,32 @@
|
|||
|
||||
<template id="web.login" name="Login">
|
||||
<t t-call="web.login_layout">
|
||||
<form class="oe_login_form" role="form" t-attf-action="/web/login" method="post" onsubmit="this.action = '/web/login' + location.hash">
|
||||
<div class="oe_structure" id="oe_structure_login_top"/>
|
||||
<owl-component t-if="not login" name="web.user_switch" />
|
||||
<form t-attf-class="oe_login_form #{'' if login else 'd-none'}" role="form" t-attf-action="/web/login" method="post" onsubmit="this.action = '/web/login' + location.hash" data-captcha="login">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
|
||||
<div class="mb-3" t-if="databases and len(databases) > 1">
|
||||
<label for="db" class="col-form-label">Database</label>
|
||||
<div t-attf-class="input-group {{'input-group-sm' if form_small else ''}}">
|
||||
<input type="text" name="db" t-att-value="request.db" id="db" t-attf-class="form-control #{'form-control-sm' if form_small else ''}" required="required" readonly="readonly"/>
|
||||
<a role="button" href="/web/database/selector" class="btn btn-secondary">Select <i class="fa fa-database" role="img" aria-label="Database" title="Database"></i></a>
|
||||
<div class="input-group">
|
||||
<input type="text" name="db" t-att-value="request.db" id="db" class="form-control" required="required" readonly="readonly"/>
|
||||
<a role="button" href="/web/database/selector" class="btn border border-start-0">Select <i class="fa fa-database" role="img" aria-label="Database" title="Database"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 field-login">
|
||||
<label for="login" class="form-label">Email</label>
|
||||
<input type="text" placeholder="Email" name="login" t-att-value="login" id="login" t-attf-class="form-control #{'form-control-sm' if form_small else ''}" required="required" autocomplete="username" autofocus="autofocus" autocapitalize="off"/>
|
||||
<label for="login" class="form-label d-flex justify-content-between">Email</label>
|
||||
<input type="text" placeholder="Enter your email" name="login" t-att-value="login" id="login" class="form-control" required="required" autocapitalize="off" autocomplete="username"/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" placeholder="Password" name="password" id="password" t-attf-class="form-control #{'form-control-sm' if form_small else ''}" required="required" autocomplete="current-password" t-att-autofocus="'autofocus' if login else None" maxlength="4096"/>
|
||||
<div class="o_caps_lock_warning">
|
||||
<label for="password" class="form-label d-flex justify-content-between">Password</label>
|
||||
<div class="input-group mb-1">
|
||||
<input type="password" placeholder="Enter your password" name="password" id="password" class="form-control" required="required" autocomplete="current-password" t-att-autofocus="'autofocus' if login else None" maxlength="4096"/>
|
||||
<button type="button" class="btn btn-sm border border-start-0 o_show_password">
|
||||
<i class="fa fa-eye"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-danger" t-if="error" role="alert">
|
||||
|
|
@ -155,16 +170,18 @@
|
|||
<t t-esc="message"/>
|
||||
</p>
|
||||
|
||||
<div t-attf-class="clearfix oe_login_buttons text-center gap-1 d-grid mb-1 {{'pt-2' if form_small else 'pt-3'}}">
|
||||
<div class="oe_login_buttons text-center d-grid mb-1 pt-3">
|
||||
<button type="submit" class="btn btn-primary">Log in</button>
|
||||
<t t-if="debug">
|
||||
<button type="submit" name="redirect" value="/web/become" class="btn btn-link btn-sm">Log in as superuser</button>
|
||||
</t>
|
||||
<div class="o_login_auth"/>
|
||||
<t t-call="web.login_oauth"/>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="type" value="password"/>
|
||||
<input type="hidden" name="redirect" t-att-value="redirect"/>
|
||||
</form>
|
||||
<div class="oe_structure" id="oe_structure_login_bottom"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
|
@ -181,13 +198,28 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="web.login_oauth" name="Login OAuth">
|
||||
<!-- Use auth_btns to insert login options displayed under the
|
||||
login form -->
|
||||
<t t-set="auth_btns" t-value="[]"/>
|
||||
<div t-if="auth_btns" class="o_login_auth">
|
||||
<em class="d-block my-3 small text-center text-muted">- or -</em>
|
||||
<div class="list-group">
|
||||
<a t-foreach="auth_btns" t-as="p" t-attf-class="{{p.get('list_item_class')}} list-group-item list-group-item-action d-flex align-items-center gap-2 py-2" t-att-href="p.get('auth_link') or '#'">
|
||||
<i t-att-class="p['css_class']" role="presentation"/>
|
||||
<span class="mx-auto" t-out="p['body']"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="web.test_helpers">
|
||||
<t t-call-assets="web.tests_assets" t-js="False"/>
|
||||
<style>
|
||||
body {
|
||||
position: relative; /* bootstrap-datepicker needs this */
|
||||
}
|
||||
body:not(.debug) .modal-backdrop, body:not(.debug) .modal, body:not(.debug) .ui-autocomplete {
|
||||
body:not(.debug) .modal-backdrop, body:not(.debug) .modal:not(.o_module_error), body:not(.debug) .ui-autocomplete {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
#qunit-testrunner-toolbar label {
|
||||
|
|
@ -202,15 +234,31 @@
|
|||
<t t-call-assets="web.tests_assets" t-css="False"/>
|
||||
</template>
|
||||
|
||||
<template id="web.unit_tests_suite">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="html_data" t-value="{'style': 'height: 100%;'}"/>
|
||||
<t t-set="title">Web Unit Tests</t>
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
|
||||
|
||||
<script type="text/javascript">
|
||||
odoo.__session_info__ = <t t-out="json.dumps(session_info)"/>;
|
||||
</script>
|
||||
|
||||
<t t-call-assets="web.assets_unit_tests_setup" />
|
||||
<t t-call-assets="web.assets_unit_tests" defer_load="True" />
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="web.qunit_suite">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="html_data" t-value="{'style': 'height: 100%;'}"/>
|
||||
<t t-set="title">Web Tests</t>
|
||||
<t t-set="head">
|
||||
<t t-call-assets="web.assets_common" t-js="false"/>
|
||||
<t t-call-assets="web.assets_backend" t-js="false"/>
|
||||
<t t-call-assets="web.tests_assets_common" t-css="false"/>
|
||||
<t t-call-assets="web.assets_backend" t-css="false"/>
|
||||
<script type="text/javascript">
|
||||
odoo.__session_info__ = <t t-out="json.dumps(session_info)"/>;
|
||||
</script>
|
||||
|
||||
<t t-call="web.test_helpers"/>
|
||||
|
||||
|
|
@ -222,118 +270,117 @@
|
|||
</t>
|
||||
</template>
|
||||
|
||||
<template id="web.qunit_mobile_suite">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="html_data" t-value="{'style': 'height: 100%;'}"/>
|
||||
<t t-set="title">Web Mobile Tests</t>
|
||||
<t t-set="head">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
|
||||
|
||||
<t t-call-assets="web.assets_common" t-js="false"/>
|
||||
<t t-call-assets="web.assets_backend" t-js="false"/>
|
||||
<t t-call-assets="web.tests_assets_common" t-css="false"/>
|
||||
<t t-call-assets="web.assets_backend" t-css="false"/>
|
||||
|
||||
<t t-call="web.test_helpers"/>
|
||||
|
||||
<t t-call-assets="web.qunit_mobile_suite_tests" t-js="false"/>
|
||||
<t t-call-assets="web.qunit_mobile_suite_tests" t-css="false"/>
|
||||
</t>
|
||||
<div id="qunit"/>
|
||||
<div id="qunit-fixture"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="web.benchmark_suite">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="html_data" t-value="{'style': 'height: 100%;'}"/>
|
||||
<t t-set="title">Web Benchmarks</t>
|
||||
<t t-set="head">
|
||||
<script type="text/javascript" src="/web/static/lib/benchmarkjs/lodash.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/benchmarkjs/benchmark.js"></script>
|
||||
|
||||
<t t-call-assets="web.assets_common" t-js="false"/>
|
||||
<t t-call-assets="web.assets_backend" t-js="false"/>
|
||||
<t t-call-assets="web.assets_common" t-css="false"/>
|
||||
<t t-call-assets="web.assets_backend" t-css="false"/>
|
||||
|
||||
<t t-call="web.test_helpers"/>
|
||||
|
||||
<script type="text/javascript">
|
||||
QUnit.config.hidepassed = false;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body:not(.debug) .modal-backdrop, body:not(.debug) .modal, body:not(.debug) .ui-autocomplete {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
#qunit-testrunner-toolbar label {
|
||||
font-weight: inherit;
|
||||
margin-bottom: inherit;
|
||||
}
|
||||
#qunit-testrunner-toolbar input[type=text] {
|
||||
width: inherit;
|
||||
display: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript" src="/web/static/tests/views/list_benchmarks.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/views/kanban_benchmarks.js"></script>
|
||||
<script type="text/javascript" src="/web/static/tests/views/form_benchmarks.js"></script>
|
||||
|
||||
</t>
|
||||
|
||||
<div id="qunit"/>
|
||||
<div id="qunit-fixture"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="web.webclient_bootstrap">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="head_web">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
|
||||
<!-- Chrome, Firefox OS and Opera -->
|
||||
<meta name="theme-color" content="#875A7B"/>
|
||||
<link rel="icon" sizes="192x192" href="/web/static/img/mobile-icons/android-192x192.png"/>
|
||||
|
||||
<!-- iOS Safari -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
|
||||
<link rel="apple-touch-icon" href="/web/static/img/mobile-icons/apple-152x152.png"/>
|
||||
|
||||
<!-- Windows Phone -->
|
||||
<meta name="msapplication-navbutton-color" content="#875A7B"/>
|
||||
<meta name="msapplication-TileColor" content="#875A7B"/>
|
||||
<meta name="msapplication-TileImage" content="/web/static/img/mobile-icons/windows-144x144.png"/>
|
||||
|
||||
<meta name="theme-color" content="#71639e"/>
|
||||
<link rel="manifest" href="/web/manifest.webmanifest" crossorigin="use-credentials"/>
|
||||
<link rel="apple-touch-icon" href="/web/static/img/odoo-icon-ios.png"/>
|
||||
<script type="text/javascript">
|
||||
odoo.__session_info__ = <t t-out="json.dumps(session_info)"/>;
|
||||
odoo.reloadMenus = () => fetch(`/web/webclient/load_menus/${odoo.__session_info__.cache_hashes.load_menus}`).then(res => res.json());
|
||||
odoo.loadMenusPromise = odoo.reloadMenus();
|
||||
// Block to avoid leaking variables in the script scope
|
||||
{
|
||||
const { user_context, cache_hashes } = odoo.__session_info__;
|
||||
// Prefetch translations to speedup webclient. This is done in JS because link rel="prefetch"
|
||||
// is not yet supported on safari.
|
||||
fetch(`/web/webclient/translations/${cache_hashes.translations}?lang=${user_context.lang}`);
|
||||
odoo.__session_info__ = <t t-out="json.dumps(session_info)"/>;
|
||||
const { user_context } = odoo.__session_info__;
|
||||
const lang = new URLSearchParams(document.location.search).get("lang");
|
||||
let menuURL = "/web/webclient/load_menus";
|
||||
if (lang) {
|
||||
user_context.lang = lang;
|
||||
menuURL += `&lang=${lang}`;
|
||||
}
|
||||
odoo.reloadMenus = () => fetch(menuURL, { cache: "no-store" }).then(res => res.json());
|
||||
odoo.loadMenusPromise = odoo.reloadMenus();
|
||||
}
|
||||
</script>
|
||||
<t t-if="request.httprequest.cookies.get('color_scheme') == 'dark'">
|
||||
<t t-call-assets="web.dark_mode_assets_common" t-js="false"/>
|
||||
<t t-call-assets="web.dark_mode_assets_backend" t-js="false"/>
|
||||
<t t-call-assets="web.assets_web_print" media="print" t-js="false"/>
|
||||
<t t-call-assets="web.assets_web" t-css="false"/>
|
||||
|
||||
<t t-if="color_scheme == 'dark'">
|
||||
<t t-call-assets="web.assets_web_dark" media="screen" t-js="false"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call-assets="web.assets_common" t-js="false"/>
|
||||
<t t-call-assets="web.assets_backend" t-js="false"/>
|
||||
<t t-call-assets="web.assets_web" media="screen" t-js="false"/>
|
||||
</t>
|
||||
<t t-call-assets="web.assets_common" t-css="false"/>
|
||||
<t t-call-assets="web.assets_backend" t-css="false"/>
|
||||
<t t-call-assets="web.assets_backend_prod_only" t-css="false"/>
|
||||
<t t-call="web.conditional_assets_tests"/>
|
||||
<t t-call="web.conditional_assets_tests" media="screen"/>
|
||||
</t>
|
||||
<t t-set="head" t-value="head_web + (head or '')"/>
|
||||
<t t-set="head" t-value="head_web + (head or '')" media="screen"/>
|
||||
<t t-set="body_classname" t-value="'o_web_client'"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="webclient_offline">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="html_data" t-value="{'style': 'height: 100%;'}"/>
|
||||
<t t-set="title">Offline</t>
|
||||
<t t-set="head">
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const cookies = document.cookie.split(';').map(c => c.trim());
|
||||
if (cookies.includes('color_scheme=dark')) {
|
||||
document.body.style.backgroundColor = "#262A36";
|
||||
document.body.style.color = "#FFFFFF";
|
||||
}
|
||||
});
|
||||
window.addEventListener('online', () => location.reload());
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
padding:0;
|
||||
margin:0;
|
||||
background-color: #fff;
|
||||
color: rgb(17, 24, 39);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
user-select: none;
|
||||
}
|
||||
.card {
|
||||
width: 80%;
|
||||
}
|
||||
.card img {
|
||||
width: 96px;
|
||||
height: auto;
|
||||
filter: grayscale(.6);
|
||||
}
|
||||
.card button {
|
||||
background-color: #714B67;
|
||||
color: #FFFFFF;
|
||||
border: 1px solid #714B67;
|
||||
border-radius: .25rem;
|
||||
padding: .5rem 1rem;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
</t>
|
||||
<div class="card">
|
||||
<img t-attf-src="data:image/png;base64,{{odoo_icon}}" alt="Odoo logo"/>
|
||||
<h1>You are offline</h1>
|
||||
<p>Check your network connection and come back here. Odoo will load as soon as you're back online.</p>
|
||||
<button onclick="location.reload()">Check again</button>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="webclient_scoped_app">
|
||||
<t t-call="web.layout">
|
||||
<t t-set="html_data" t-value="{'style': 'height: 100%;'}"/>
|
||||
<t t-set="title" t-value="'Install %s' % (app_name)"/>
|
||||
<t t-set="x_icon" t-value="'/scoped_app_icon_png?app_id=%s' % (app_id)" />
|
||||
<t t-set="head">
|
||||
<t t-call-assets="web.assets_frontend_minimal"/>
|
||||
<t t-call-assets="web.assets_frontend_lazy"/>
|
||||
<link rel="apple-touch-icon" t-att-href="x_icon"/>
|
||||
<link rel="manifest" t-att-href="safe_manifest_url" crossorigin="use-credentials" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
</t>
|
||||
<owl-component name="web.install_scoped_app" />
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue