'use strict';
'require form';
'require fs';
'require poll';
'require uci';
'require ui';
'require dockerman.common as dm2';

/*
Copyright 2026
Docker manager JS for Luci by Paul Donald <newtwen+github@gmail.com> 
Based on Docker Lua by lisaac <https://github.com/lisaac/luci-app-dockerman>
LICENSE: GPLv2.0
*/

const dummy_stats = {"read":"2026-01-08T22:57:31.547920715Z","pids_stats":{"current":3},"networks":{"eth0":{"rx_bytes":5338,"rx_dropped":0,"rx_errors":0,"rx_packets":36,"tx_bytes":648,"tx_dropped":0,"tx_errors":0,"tx_packets":8},"eth5":{"rx_bytes":4641,"rx_dropped":0,"rx_errors":0,"rx_packets":26,"tx_bytes":690,"tx_dropped":0,"tx_errors":0,"tx_packets":9}},"memory_stats":{"stats":{"total_pgmajfault":0,"cache":0,"mapped_file":0,"total_inactive_file":0,"pgpgout":414,"rss":6537216,"total_mapped_file":0,"writeback":0,"unevictable":0,"pgpgin":477,"total_unevictable":0,"pgmajfault":0,"total_rss":6537216,"total_rss_huge":6291456,"total_writeback":0,"total_inactive_anon":0,"rss_huge":6291456,"hierarchical_memory_limit":67108864,"total_pgfault":964,"total_active_file":0,"active_anon":6537216,"total_active_anon":6537216,"total_pgpgout":414,"total_cache":0,"inactive_anon":0,"active_file":0,"pgfault":964,"inactive_file":0,"total_pgpgin":477},"max_usage":6651904,"usage":6537216,"failcnt":0,"limit":67108864},"blkio_stats":{},"cpu_stats":{"cpu_usage":{"percpu_usage":[8646879,24472255,36438778,30657443],"usage_in_usermode":50000000,"total_usage":100215355,"usage_in_kernelmode":30000000},"system_cpu_usage":739306590000000,"online_cpus":4,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"precpu_stats":{"cpu_usage":{"percpu_usage":[8646879,24350896,36438778,30657443],"usage_in_usermode":50000000,"total_usage":100093996,"usage_in_kernelmode":30000000},"system_cpu_usage":9492140000000,"online_cpus":4,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}}};
const dummy_ps = {"Titles":["UID","PID","PPID","C","STIME","TTY","TIME","CMD"],"Processes":[["root","13642","882","0","17:03","pts/0","00:00:00","/bin/bash"],["root","13735","13642","0","17:06","pts/0","00:00:00","sleep 10"]]};
const dummy_changes = [{"Path":"/dev","Kind":0},{"Path":"/dev/kmsg","Kind":1},{"Path":"/test","Kind":1}];

// https://docs.docker.com/reference/api/engine/version/v1.47/#tag/Container/operation/ContainerStats
// Helper function to calculate memory usage percentage
function calculateMemoryUsage(stats) {
	if (!stats || !stats.memory_stats) return null;
	const mem = stats.memory_stats;
	if (!mem.usage || !mem.limit) return null;

	// used_memory = memory_stats.usage - memory_stats.stats.cache
	const cache = mem.stats?.cache || 0;
	const used_memory = mem.usage - cache;
	const available_memory = mem.limit;

	// Memory usage % = (used_memory / available_memory) * 100.0
	const percentage = (used_memory / available_memory) * 100.0;

	return {
		percentage: percentage,
		used: used_memory,
		limit: available_memory
	};
}

// Helper function to calculate CPU usage percentage
// Pass previousStats if Docker API doesn't provide complete precpu_stats
function calculateCPUUsage(stats, previousStats) {
	if (!stats || !stats.cpu_stats) return null;
	const cpu = stats.cpu_stats;

	// Try to use precpu_stats from API first, fall back to our stored previous stats
	let precpu = stats.precpu_stats;

	// If precpu_stats is incomplete, use our manually stored previous stats
	if (!precpu || !precpu.system_cpu_usage) {
		if (previousStats && previousStats.cpu_stats) {
			// console.log('Using manually stored previous CPU stats');
			precpu = previousStats.cpu_stats;
		} else {
			// console.log('No previous CPU stats available yet - waiting for next cycle');
			return null;
		}
	}

	// If we don't have both cpu_stats and precpu_stats, return null
	if (!cpu.cpu_usage || !precpu || !precpu.cpu_usage) {
		// console.log('CPU stats incomplete:', { 
		// 	hasCpu: !!cpu.cpu_usage, 
		// 	hasPrecpu: !!precpu,
		// 	hasPrecpuUsage: !!(precpu && precpu.cpu_usage)
		// });
		return null;
	}

	// Validate we have the required fields
	const validationChecks = {
		'cpu.cpu_usage.total_usage': typeof cpu.cpu_usage.total_usage,
		'precpu.cpu_usage.total_usage': typeof precpu.cpu_usage.total_usage,
		'cpu.system_cpu_usage': typeof cpu.system_cpu_usage,
		'precpu.system_cpu_usage': typeof precpu.system_cpu_usage,
		'cpu_values': {
			cpu_total: cpu.cpu_usage.total_usage,
			precpu_total: precpu.cpu_usage.total_usage,
			cpu_system: cpu.system_cpu_usage,
			precpu_system: precpu.system_cpu_usage
		}
	};

	// Check if we have valid numeric values for all required fields
	// Note: precpu_stats may be empty/undefined on first stats call
	if (typeof cpu.cpu_usage.total_usage !== 'number' || 
		typeof precpu.cpu_usage.total_usage !== 'number' ||
		typeof cpu.system_cpu_usage !== 'number' ||
		typeof precpu.system_cpu_usage !== 'number') {
		// console.log('CPU stats incomplete - waiting for valid precpu data:', validationChecks);
		return null;
	}

	// Also check if precpu data is essentially zero (first call scenario)
	if (precpu.cpu_usage.total_usage === 0 || precpu.system_cpu_usage === 0) {
		// console.log('CPU precpu stats are zero - waiting for next stats cycle');
		return null;
	}

	// cpu_delta = cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage
	const cpu_delta = cpu.cpu_usage.total_usage - precpu.cpu_usage.total_usage;

	// system_cpu_delta = cpu_stats.system_cpu_usage - precpu_stats.system_cpu_usage
	const system_cpu_delta = cpu.system_cpu_usage - precpu.system_cpu_usage;

	// Validate deltas
	if (system_cpu_delta <= 0 || cpu_delta < 0) {
		// console.warn('Invalid CPU deltas:', { 
		// 	cpu_delta, 
		// 	system_cpu_delta,
		// 	cpu_total: cpu.cpu_usage.total_usage,
		// 	precpu_total: precpu.cpu_usage.total_usage,
		// 	system: cpu.system_cpu_usage,
		// 	presystem: precpu.system_cpu_usage
		// });
		return null;
	}

	// number_cpus = length(cpu_stats.cpu_usage.percpu_usage) or cpu_stats.online_cpus
	const number_cpus = cpu.online_cpus || (cpu.cpu_usage.percpu_usage?.length || 1);

	// CPU usage % = (cpu_delta / system_cpu_delta) * number_cpus * 100.0
	const percentage = (cpu_delta / system_cpu_delta) * number_cpus * 100.0;

	// console.log('CPU calculation:', { 
	// 	cpu_delta, 
	// 	system_cpu_delta, 
	// 	number_cpus, 
	// 	percentage: percentage.toFixed(2) + '%'
	// });

	return {
		percentage: percentage,
		number_cpus: number_cpus
	};
}

// Helper function to create a progress bar
function createProgressBar(label, percentage, used, total) {
	const clampedPercentage = Math.min(Math.max(percentage || 0, 0), 100);
	const color = clampedPercentage > 90 ? '#d9534f' : (clampedPercentage > 70 ? '#f0ad4e' : '#5cb85c');

	return E('div', { 'style': 'margin: 10px 0;' }, [
		E('div', { 'style': 'display: flex; justify-content: space-between; margin-bottom: 5px;' }, [
			E('span', { 'style': 'font-weight: bold;' }, label),
			E('span', {}, used && total ? `${used} / ${total}` : `${clampedPercentage.toFixed(2)}%`)
		]),
		E('div', { 
			'style': 'width: 100%; height: 20px; background-color: #e9ecef; border-radius: 4px; overflow: hidden;'
		}, [
			E('div', {
				'style': `width: ${clampedPercentage}%; height: 100%; background-color: ${color}; transition: width 0.3s ease;`
			})
		])
	]);
}


const ChangeTypes = Object.freeze({
	0: 'Modified',
	1: 'Added',
	2: 'Deleted',
});

return dm2.dv.extend({
	load() {
		const requestPath = L.env.requestpath;
		const containerId = requestPath[requestPath.length-1] || '';
		this.psArgs = uci.get('dockerd', 'globals', 'ps_flags') || '-ww';

		// First load container info to check state
		return dm2.container_inspect({id: containerId})
			.then(container => {
				if (container.code !== 200) window.location.href = `${this.dockerman_url}/containers`;
				const this_container = container.body || {};

				// Now load other resources, conditionally calling stats/ps/changes only if running
				const isRunning = this_container.State?.Status === 'running';

				return Promise.all([
					this_container,
					dm2.image_list().then(images => {
						return Array.isArray(images.body) ? images.body : [];
					}),
					dm2.network_list().then(networks => {
						return Array.isArray(networks.body) ? networks.body : [];
					}),
					dm2.docker_info().then(info => {
						const numcpus = info.body?.NCPU || 1.0;
						const memory = info.body?.MemTotal || 2**10;
						return {numcpus: numcpus, memory: memory};
					}),
					isRunning ? dm2.container_top({ id: containerId, query: { 'ps_args': this.psArgs || '-ww' } })
						.then(res => {
							if (res?.code < 300 && res.body) return res.body;
							else return dummy_ps;
						})
						.catch(() => {}) : Promise.resolve(),
					isRunning ? dm2.container_stats({ id: containerId, query: { 'stream': false, 'one-shot': true } })
						.then(res => {
							if (res?.code < 300 && res.body) return res.body;
							else return dummy_stats;
						})
						.catch(() => {}) : Promise.resolve(),
					dm2.container_changes({ id: containerId })
						.then(res => {
							if (res?.code < 300 && Array.isArray(res.body)) return res.body;
							else return dummy_changes;
						})
						.catch(() => {}),
				]);
			});
	},

	buildList(array, mapper) {
		if (!Array.isArray(array)) return [];
		const out = [];
		for (const item of array) {
			const mapped = mapper(item);
			if (mapped || mapped === 0)
				out.push(mapped);
		}
		return out;
	},

	buildListFromObject(obj, mapper) {
		if (!obj || typeof obj !== 'object') return [];
		const out = [];
		for (const [k, v] of Object.entries(obj)) {
			const mapped = mapper(k, v);
			if (mapped || mapped === 0)
				out.push(mapped);
		}
		return out;
	},

	getMountsList(this_container) {
		return this.buildList(this_container?.Mounts, (mount) => {
			if (!mount?.Type || !mount?.Destination) return null;
			let entry = `${mount.Type}:${mount.Source}:${mount.Destination}`;
			if (mount.Mode) entry += `:${mount.Mode}`;
			return entry;
		});
	},

	getPortsList(this_container) {
		const portBindings = this_container?.HostConfig?.PortBindings;
		if (!portBindings || typeof portBindings !== 'object') return [];
		const ports = [];
		for (const [containerPort, bindings] of Object.entries(portBindings)) {
			if (Array.isArray(bindings)) {
				for (const b of bindings) {
					if (!b?.HostPort) continue;
					const ip = (b.HostIp && b.HostIp !== '0.0.0.0' && b.HostIp !== '::') ? b.HostIp + ':' : '';
					ports.push(`${ip}${b.HostPort}:${containerPort}`);
				}
			}
		}
		return ports;
	},

	getEnvList(this_container) {
		return this_container?.Config?.Env || [];
	},

	getDevicesList(this_container) {
		return this.buildList(this_container?.HostConfig?.Devices, (device) => {
			if (!device?.PathOnHost || !device?.PathInContainer) return null;
			let entry = `${device.PathOnHost}:${device.PathInContainer}`;
			if (device.CgroupPermissions) entry += `:${device.CgroupPermissions}`;
			return entry;
		});
	},

	getTmpfsList(this_container) {
		return this.buildListFromObject(this_container?.HostConfig?.Tmpfs, (path, opts) => `${path}${opts ? ':' + opts : ''}`);
	},

	getDnsList(this_container) {
		return this_container?.HostConfig?.Dns || [];
	},

	getSysctlList(this_container) {
		return this.buildListFromObject(this_container?.HostConfig?.Sysctls, (key, value) => `${key}:${value}`);
	},

	getCapAddList(this_container) {
		return this_container?.HostConfig?.CapAdd || [];
	},

	getLogOptList(this_container) {
		return this.buildListFromObject(this_container?.HostConfig?.LogConfig?.Config, (key, value) => `${key}=${value}`);
	},

	getCNetworksArray(c_networks, networks) {
		if (!c_networks || typeof c_networks !== 'object') return [];
		const data = [];

		for (const [name, net] of Object.entries(c_networks)) {
			const network = networks.find(n => n.Name === name || n.Id === name);
			const netid = !net?.NetworkID ? network?.Id : net?.NetworkID;

			/* Even if netid is null, proceed: perhaps the network was deleted. If we
			display it, the user can disconnect it. */
			data.push({
				...net,
				_shortId: netid?.substring(0,12) ||  '',
				Name: name,
				NetworkID: netid,
				DNSNames: net?.DNSNames || '',
				IPv4Address: net?.IPAMConfig?.IPv4Address || net?.IPAddress || '',
				IPv6Address: net?.IPAMConfig?.IPv6Address || '',
				Aliases: net?.Aliases || '',
			});
		}

		return data;
	},

	render([this_container, images, networks, cpus_mem, ps_top, stats_data, changes_data]) {
		this.networks = networks;
		const view = this;
		const containerName = this_container.Name?.substring(1) || this_container.Id || '';
		const containerIdShort = (this_container.Id || '').substring(0, 12);
		const c_networks = this_container.NetworkSettings?.Networks || {};

		// Create main container with action buttons
		const mainContainer = E('div', {});

		const containerStatus = this.getContainerStatus(this_container);

		// Add title and description
		const header = E('div', { 'class': 'cbi-page' }, [
			E('h2', {}, _('Docker - Container')),
			E('p', { 'style': 'margin: 10px 0; display: flex; gap: 6px; align-items: center;' }, [
				this.wrapStatusText(containerName, containerStatus, 'font-weight:600;'),
				E('span', { 'style': 'color:#666;' }, `(${containerIdShort})`)
			]),
			E('p', { 'style': 'color: #666;' }, _('Manage and view container configuration'))
		]);
		mainContainer.appendChild(header);

		// Add action buttons section
		const buttonSection = E('div', { 'class': 'cbi-section', 'style': 'margin-bottom: 20px;' });
		const buttonContainer = E('div', { 'style': 'display: flex; gap: 10px; flex-wrap: wrap;' });

		// Start button
		if (containerStatus !== 'running') {
			const startBtn = E('button', {
				'class': 'cbi-button cbi-button-apply',
				'click': (ev) => this.executeAction(ev, 'start', this_container.Id)
			}, [_('Start')]);
			buttonContainer.appendChild(startBtn);
		}

		// Restart button
		if (containerStatus === 'running') {
			const restartBtn = E('button', {
				'class': 'cbi-button cbi-button-reload',
				'click': (ev) => this.executeAction(ev, 'restart', this_container.Id)
			}, [_('Restart')]);
			buttonContainer.appendChild(restartBtn);
		}

		// Stop button
		if (containerStatus === 'running' || containerStatus === 'paused' || containerStatus === 'restarting') {
			const stopBtn = E('button', {
				'class': 'cbi-button cbi-button-reset',
				'click': (ev) => this.executeAction(ev, 'stop', this_container.Id)
			}, [_('Stop')]);
			buttonContainer.appendChild(stopBtn);
		}

		// Kill button
		if (containerStatus === 'running' || containerStatus === 'restarting') {
			const killBtn = E('button', {
				'class': 'cbi-button',
				'style': 'background-color: #dc3545;',
				'click': (ev) => this.executeAction(ev, 'kill', this_container.Id)
			}, [_('Kill')]);
			buttonContainer.appendChild(killBtn);
		}

		// Pause/Unpause button
		if (containerStatus === 'running' || containerStatus === 'paused') {
			const isPausedNow = this.container?.State?.Paused === true;
			const pauseBtn = E('button', {
				'class': 'cbi-button',
				'id': 'pause-button',
				'click': (ev) => {
					const currentStatus = this.getContainerStatus(this_container);
					this.executeAction(ev, (currentStatus === 'paused' ? 'unpause' : 'pause'), this_container.Id);
				}
			}, [isPausedNow ? _('Unpause') : _('Pause')]);
			buttonContainer.appendChild(pauseBtn);
		}

		// Duplicate button
		const duplicateBtn = E('button', {
			'class': 'cbi-button cbi-button-add',
			'click': (ev) => {
				ev.preventDefault();
				window.location.href = `${this.dockerman_url}/container_new/duplicate/${this_container.Id}`;
			}
		}, [_('Duplicate/Edit')]);
		buttonContainer.appendChild(duplicateBtn);

		// Export button
		const exportBtn = E('button', {
			'class': 'cbi-button cbi-button-reload',
			'click': (ev) => {
				ev.preventDefault();
				window.location.href = `${this.dockerman_url}/container/export/${this_container.Id}`;
			}
		}, [_('Export')]);
		buttonContainer.appendChild(exportBtn);

		// Remove button
		const removeBtn = E('button', {
			'class': 'cbi-button cbi-button-remove',
			'click': (ev) => this.executeAction(ev, 'remove', this_container.Id),
		}, [_('Remove')]);
		buttonContainer.appendChild(removeBtn);

		// Back button
		const backBtn = E('button', {
			'class': 'cbi-button',
			'click': () => window.location.href = `${this.dockerman_url}/containers`,
		}, [_('Back to Containers')]);
		buttonContainer.appendChild(backBtn);

		buttonSection.appendChild(buttonContainer);
		mainContainer.appendChild(buttonSection);


		const m = new form.JSONMap({
			cont: this_container,
			nets: this.getCNetworksArray(c_networks, networks),
			hostcfg: this_container.HostConfig || {},
		}, null);
		m.submit = false;
		m.reset = false;

		let s = m.section(form.NamedSection, 'cont', null, _('Container detail'));
		s.anonymous = true;
		s.nodescriptions = true;
		s.addremove = false;

		let o, ss;

		s.tab('info', _('Info'));

		o = s.taboption('info', form.Value, 'Name', _('Name'));

		o = s.taboption('info', form.DummyValue, 'Id', _('ID'));

		o = s.taboption('info', form.DummyValue, 'Image', _('Image'));
		o.cfgvalue = (sid) => this.getImageFirstTag(images, this.map.data.data[sid].Image);

		o = s.taboption('info', form.DummyValue, 'Image', _('Image ID'));

		o = s.taboption('info', form.DummyValue, 'status', _('Status'));
		o.cfgvalue = (sid) => this.map.data.data[sid].State?.Status || '';

		o = s.taboption('info', form.DummyValue, 'Created', _('Created'));

		o = s.taboption('info', form.DummyValue, 'started', _('Finish Time'));
		o.cfgvalue = () => {
			if (this_container.State?.Running)
				return this_container.State?.StartedAt || '-';
			return this_container.State?.FinishedAt || '-';
		};

		o = s.taboption('info', form.DummyValue, 'healthy', _('Health Status'));
		o.cfgvalue = () => this_container.State?.Health?.Status || '-';

		o = s.taboption('info', form.DummyValue, 'user', _('User'));
		o.cfgvalue = () => this_container.Config?.User || '-';

		o = s.taboption('info', form.ListValue, 'restart_policy', _('Restart Policy'));
		o.cfgvalue = () => this_container.HostConfig?.RestartPolicy?.Name || '-';
		o.value('no', _('No'));
		o.value('unless-stopped', _('Unless stopped'));
		o.value('always', _('Always'));
		o.value('on-failure', _('On failure'));

		o = s.taboption('info', form.DummyValue, 'hostname', _('Host Name'));
		o.cfgvalue = () => this_container.Config?.Hostname || '-';

		o = s.taboption('info', form.DummyValue, 'command', _('Command'));
		o.cfgvalue = () => {
			const cmd = this_container.Config?.Cmd;
			if (Array.isArray(cmd))
				return cmd.join(' ');
			return cmd || '-';
		};

		o = s.taboption('info', form.DummyValue, 'env', _('Env'));
		o.rawhtml = true;
		o.cfgvalue = () => {
			const env = this.getEnvList(this_container);
			return env.length > 0 ? env.join('<br />') : '-';
		};

		o = s.taboption('info', form.DummyValue, 'ports', _('Ports'));
		o.rawhtml = true;
		o.cfgvalue = () => {
			const ports = view.getPortsList(this_container);
			return ports.length > 0 ? ports.join('<br />') : '-';
		};

		o = s.taboption('info', form.DummyValue, 'links', _('Links'));
		o.rawhtml = true;
		o.cfgvalue = () => {
			const links = this_container.HostConfig?.Links;
			return Array.isArray(links) && links.length > 0 ? links.join('<br />') : '-';
		};

		o = s.taboption('info', form.DummyValue, 'devices', _('Devices'));
		o.rawhtml = true;
		o.cfgvalue = () => {
			const devices = this.getDevicesList(this_container);
			return devices.length > 0 ? devices.join('<br />') : '-';
		};

		o = s.taboption('info', form.DummyValue, 'tmpfs', _('Tmpfs Directories'));
		o.rawhtml = true;
		o.cfgvalue = () => {
			const tmpfs = this.getTmpfsList(this_container);
			return tmpfs.length > 0 ? tmpfs.join('<br />') : '-';
		};

		o = s.taboption('info', form.DummyValue, 'dns', _('DNS'));
		o.rawhtml = true;
		o.cfgvalue = () => {
			const dns = view.getDnsList(this_container);
			return dns.length > 0 ? dns.join('<br />') : '-';
		};

		o = s.taboption('info', form.DummyValue, 'sysctl', _('Sysctl Settings'));
		o.rawhtml = true;
		o.cfgvalue = () => {
			const sysctl = this.getSysctlList(this_container);
			return sysctl.length > 0 ? sysctl.join('<br />') : '-';
		};

		o = s.taboption('info', form.DummyValue, 'mounts', _('Mounts/Binds'));
		o.rawhtml = true;
		o.cfgvalue = () => {
			const mounts = view.getMountsList(this_container);
			return mounts.length > 0 ? mounts.join('<br />') : '-';
		};

		// NETWORKS TAB
		s.tab('network', _('Networks'));

		o = s.taboption('network', form.SectionValue, '__net__', form.TableSection, 'nets', null);
		ss = o.subsection;
		ss.anonymous = true;
		ss.nodescriptions = true;
		ss.addremove = true;
		ss.addbtntitle = _('Connect') + ' 🔗';
		ss.delbtntitle = _('Disconnect') + ' ⛓️‍💥';

		o = ss.option(form.DummyValue, 'Name', _('Name'));

		o = ss.option(form.DummyValue, '_shortId', _('ID'));
		o.cfgvalue = function(section_id, value) {
			const name_links = false;
			const nets = this.map.data.data[section_id] || {};
			return view.parseNetworkLinksForContainer(networks, (Array.isArray(nets) ? nets : [nets]), name_links);
		};

		o = ss.option(form.DummyValue, 'IPv4Address', _('IPv4 Address'));

		o = ss.option(form.DummyValue, 'IPv6Address', _('IPv6 Address'));

		o = ss.option(form.DummyValue, 'GlobalIPv6Address', _('Global IPv6 Address'));

		o = ss.option(form.DummyValue, 'MacAddress', _('MAC Address'));

		o = ss.option(form.DummyValue, 'Gateway', _('Gateway'));

		o = ss.option(form.DummyValue, 'IPv6Gateway', _('IPv6 Gateway'));

		o = ss.option(form.DummyValue, 'DNSNames', _('DNS Names'));

		o = ss.option(form.DummyValue, 'Aliases', _('Aliases'));

		ss.handleAdd = function(ev) {
			ev.preventDefault();
			view.executeNetworkAction('connect', null, null, this_container);
		};

		ss.handleRemove = function(section_id, ev) {
			const network = this.map.data.data[section_id];
			ev.preventDefault();
			delete this.map.data.data[section_id];
			this.super('handleRemove', [ev]);
			view.executeNetworkAction('disconnect', (network.NetworkID || network.Name), network.Name, this_container);
		};



		s.tab('resources', _('Resources'));

		o = s.taboption('resources', form.SectionValue, '__hcfg__', form.TypedSection, 'hostcfg', null);
		ss = o.subsection;
		ss.anonymous = true;
		ss.nodescriptions = false;
		ss.addremove = false;

		o = ss.option(form.Value, 'NanoCpus', _('CPUs'));
		o.cfgvalue = (sid) => view.map.data.data[sid].NanoCpus / (10**9);
		o.placeholder='1.5';
		o.datatype = 'ufloat';
		o.validate = function(section_id, value) {
			if (!value) return true;
			if (value > cpus_mem.numcpus) return _(`Only ${cpus_mem.numcpus} CPUs available`);
			return true;
		};

		o = ss.option(form.Value, 'CpuPeriod', _('CPU Period (microseconds)'));
		o.datatype = 'or(and(uinteger,min(1000),max(1000000)),"0")';

		o = ss.option(form.Value, 'CpuQuota', _('CPU Quota (microseconds)'));
		o.datatype = 'uinteger';

		o = ss.option(form.Value, 'CpuShares', _('CPU Shares Weight'));
		o.placeholder='1024';
		o.datatype = 'uinteger';

		o = ss.option(form.Value, 'Memory', _('Memory Limit'));
		o.cfgvalue = (sid, val) => {
			const mem = view.map.data.data[sid].Memory;
			return mem ? '%1024.2m'.format(mem) : 0;
		};
		o.write = function(sid, val) {
			if (!val || val == 0) return 0;
			this.map.data.data[sid].Memory = view.parseMemory(val);
			return view.parseMemory(val) || 0;
		};
		o.validate = function(sid, value) {
			if (!value) return true;
			if (value > view.memory) return _(`Only ${view.memory} bytes available`);
			return true;
		};

		o = ss.option(form.Value, 'MemorySwap', _('Memory + Swap'));
		o.cfgvalue = (sid, val) => {
			const swap = this.map.data.data[sid].MemorySwap;
			return swap ? '%1024.2m'.format(swap) : 0;
		};
		o.write = function(sid, val) {
			if (!val || val == 0) return 0;
			this.map.data.data[sid].MemorySwap = view.parseMemory(val);
			return view.parseMemory(val) || 0;
		};

		o = ss.option(form.Value, 'MemoryReservation', _('Memory Reservation'));
		o.cfgvalue = (sid, val) => {
			const res = this.map.data.data[sid].MemoryReservation;
			return res ? '%1024.2m'.format(res) : 0;
		};
		o.write = function(sid, val) {
			if (!val || val == 0) return 0;
			this.map.data.data[sid].MemoryReservation = view.parseMemory(val);
			return view.parseMemory(val) || 0;
		};

		o = ss.option(form.Flag, 'OomKillDisable', _('OOM Kill Disable'));

		o = ss.option(form.Value, 'BlkioWeight', _('Block IO Weight'));
		o.datatype = 'and(uinteger,min(0),max(1000)';

		o = ss.option(form.DummyValue, 'Privileged', _('Privileged Mode'));
		o.cfgvalue = (sid, val) => this.map.data.data[sid]?.Privileged ? _('Yes') : _('No');

		o = ss.option(form.DummyValue, 'CapAdd', _('Added Capabilities'));
		o.cfgvalue = (sid, val) => {
			const caps = this.map.data.data[sid]?.CapAdd;
			return Array.isArray(caps) && caps.length > 0 ? caps.join(', ') : '-';
		};

		o = ss.option(form.DummyValue, 'CapDrop', _('Dropped Capabilities'));
		o.cfgvalue = (sid, val) => {
			const caps = this.map.data.data[sid]?.CapDrop;
			return Array.isArray(caps) && caps.length > 0 ? caps.join(', ') : '-';
		};

		o = ss.option(form.DummyValue, 'LogDriver', _('Log Driver'));
		o.cfgvalue = (sid) => this.map.data.data[sid].LogConfig?.Type || '-';

		o = ss.option(form.DummyValue, 'log_opt', _('Log Options'));
		o.cfgvalue = () => {
			const opts = this.getLogOptList(this_container);
			return opts.length > 0 ? opts.join('<br />') : '-';
		};

		// STATS TAB
		s.tab('stats', _('Stats'));

		function updateStats(stats_data) {
			const status = view.getContainerStatus(this_container);

			if (status !== 'running') {
				// If we already have UI elements, clear/update them
				if (view.statsTable) {
					const progressBarsSection = document.getElementById('stats-progress-bars');
					if (progressBarsSection) {
						progressBarsSection.innerHTML = '';
						progressBarsSection.appendChild(E('p', {}, _('Container is not running') + ' (' + _('Status') + ': ' + status + ')'));
					}
					try { view.statsTable.update([]); } catch (e) {}
				}

				return E('div', { 'class': 'cbi-section' }, [
					E('p', {}, [
						_('Container is not running') + ' (' + _('Status') + ': ' + status + ')'
					])
				]);
			}

			const stats = stats_data || dummy_stats;

			// Calculate usage percentages
			const memUsage = calculateMemoryUsage(stats);
			const cpuUsage = calculateCPUUsage(stats, view.previousCpuStats);

			// Store current stats for next calculation
			view.previousCpuStats = stats;

			// Prepare rows
			const rows = [
				[_('PID Stats'), view.objectToText(stats.pids_stats)],
				[_('Net Stats'), view.objectToText(stats.networks)],
				[_('Mem Stats'), view.objectToText(stats.memory_stats)],
				[_('BlkIO Stats'), view.objectToText(stats.blkio_stats)],
				[_('CPU Stats'), view.objectToText(stats.cpu_stats)],
				[_('Per CPU Stats'), view.objectToText(stats.precpu_stats)]
			];

			// If table already exists (polling update), update in-place
			if (view.statsTable) {
				try {
					view.statsTable.update(rows);
				} catch (e) { console.error('Failed to update stats table', e); }

				// Update progress bars
				const progressBarsSection = document.getElementById('stats-progress-bars');
				if (progressBarsSection) {
					progressBarsSection.innerHTML = '';
					progressBarsSection.appendChild(E('h3', {}, _('Resource Usage')));
					progressBarsSection.appendChild(
						memUsage ? createProgressBar(
							_('Memory Usage'),
							memUsage.percentage,
							'%1024.2m'.format(memUsage.used),
							'%1024.2m'.format(memUsage.limit)
						) : E('div', {}, _('Memory usage data unavailable'))
					);
					progressBarsSection.appendChild(
						cpuUsage ? createProgressBar(
							_('CPU Usage') + ` (${cpuUsage.number_cpus} CPUs)`,
							cpuUsage.percentage,
							null,
							null
						) : E('div', {}, _('CPU usage data unavailable'))
					);
				}

				// Update raw JSON field
				const statsField = document.getElementById('raw-stats-field');
				if (statsField) statsField.textContent = JSON.stringify(stats, null, 2);

				return true;
			}

			// Create progress bars section (initial render)
			const progressBarsSection = E('div', { 
				'class': 'cbi-section',
				'id': 'stats-progress-bars',
				'style': 'margin-bottom: 20px;'
			}, [
				E('h3', {}, _('Resource Usage')),
				memUsage ? createProgressBar(
					_('Memory Usage'),
					memUsage.percentage,
					'%1024.2m'.format(memUsage.used),
					'%1024.2m'.format(memUsage.limit)
				) : E('div', {}, _('Memory usage data unavailable')),
				cpuUsage ? createProgressBar(
					_('CPU Usage') + ` (${cpuUsage.number_cpus} CPUs)`,
					cpuUsage.percentage,
					null,
					null
				) : E('div', {}, _('CPU usage data unavailable'))
			]);

			const statsTable = new L.ui.Table(
				[_('Metric'), _('Value')],
				{ id: 'stats-table' },
				E('em', [_('No statistics available')])
			);

			// Store table reference for poll updates
			view.statsTable = statsTable;

			// Initial data
			statsTable.update(rows);

			return E('div', { 'class': 'cbi-section' }, [
				progressBarsSection,
				statsTable.render(),
				E('h3', { 'style': 'margin-top: 20px;' }, _('Raw JSON')),
				E('pre', { 
					style: 'overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;', 
					id: 'raw-stats-field' 
				}, JSON.stringify(stats, null, 2))
			]);
		};

		// Create custom table for stats using L.ui.Table
		o = s.taboption('stats', form.DummyValue, '_stats_table', _('Container Statistics'));
		o.render = L.bind(() => { return updateStats(stats_data)}, this);

		// PROCESS TAB
		s.tab('ps', _('Processes'));

		// Create custom table for processes using L.ui.Table
		o = s.taboption('ps', form.DummyValue, '_ps_table', _('Running Processes'));
		o.render = L.bind(() => {
			const status = this.getContainerStatus(this_container);

			if (status !== 'running') {
				return E('div', { 'class': 'cbi-section' }, [
					E('p', {}, [
						_('Container is not running') + ' (' + _('Status') + ': ' + status + ')'
					])
				]);
			}

			// Use titles from the loaded data, or fallback to default
			const titles = (ps_top && ps_top.Titles) ? ps_top.Titles : 
				[_('PID'), _('USER'), _('VSZ'), _('STAT'), _('COMMAND')];

			// Store raw titles (without translation) for comparison in poll
			this.psTitles = titles;

			const psTable = new L.ui.Table(
				titles.map(t => _(t)),
				{ id: 'ps-table' },
				E('em', [_('No processes running')])
			);

			// Store table reference and titles for poll updates
			this.psTable = psTable;
			this.psTitles = titles;

			// Initial data from dummy_ps
			if (ps_top && ps_top.Processes) {
				psTable.update(ps_top.Processes);
			}

			return E('div', { 'class': 'cbi-section' }, [
				E('div', { 'style': 'margin-bottom: 10px;' }, [
					E('label', { 'for': 'ps-flags-input', 'style': 'margin-right: 8px;' }, _('ps flags:')),
					E('input', {
						id: 'ps-flags-input',
						'class': 'cbi-input-text',
						'type': 'text',
						'value': this.psArgs || '-ww',
						'placeholder': '-ww',
						'style': 'width: 200px;',
						'input': (ev) => { this.psArgs = ev.target.value || '-ww'; }
					})
				]),
				psTable.render(),
				E('h3', { 'style': 'margin-top: 20px;' }, _('Raw JSON')),
				E('pre', { 
					style: 'overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;', 
					id: 'raw-ps-field' 
				}, JSON.stringify(ps_top || dummy_ps, null, 2))
			]);
		}, this);

		// CHANGES TAB
		s.tab('changes', _('Changes'));

		// Create custom table for changes using L.ui.Table
		o = s.taboption('changes', form.DummyValue, '_changes_table', _('Filesystem Changes'));
		o.render = L.bind(() => {
			const changesTable = new L.ui.Table(
				[_('Kind'), _('Path')],
				{ id: 'changes-table' },
				E('em', [_('No filesystem changes detected')])
			);

			// Store table reference for poll updates
			this.changesTable = changesTable;

			// Initial data
			const rows = (changes_data || dummy_changes).map(change => [
				ChangeTypes[change?.Kind] || change?.Kind,
				change?.Path
			]);
			changesTable.update(rows);

			return E('div', { 'class': 'cbi-section' }, [
				changesTable.render(),
				E('h3', { 'style': 'margin-top: 20px;' }, _('Raw JSON')),
				E('pre', { 
					style: 'overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;', 
					id: 'raw-changes-field' 
				}, JSON.stringify(changes_data || dummy_changes, null, 2))
			]);
		}, this);



		// FILE TAB
		s.tab('file', _('File'));
		let fileDiv = null;

		o = s.taboption('file', form.DummyValue, 'json', '_file');
		o.cfgvalue = (sid, val) => '/';
		o.render = L.bind(() => {
			if (fileDiv) {
				return fileDiv;
			}

			fileDiv = E('div', { 'class': 'cbi-section' }, [
				E('div', { 'style': 'margin-bottom: 10px;' }, [
					E('label', { 'style': 'margin-right: 10px;' }, _('Path:')),
					E('input', {
						'type': 'text',
						'id': 'file-path',
						'class': 'cbi-input-text',
						'value': '/',
						'style': 'width: 200px;'
					}),
					E('button', {
						'class': 'cbi-button cbi-button-positive',
						'style': 'margin-left: 10px;',
						'click': () => this.handleFileUpload(this_container.Id),
					}, _('Upload') + ' ⬆️'),
					E('button', {
						'class': 'cbi-button cbi-button-neutral',
						'style': 'margin-left: 5px;',
						'click': () => this.handleFileDownload(this_container.Id),
					}, _('Download') + ' ⬇️'),
					E('button', {
						'class': 'cbi-button cbi-button-neutral',
						'style': 'margin-left: 5px;',
						'click': () => this.handleInfoArchive(this_container.Id),
					}, _('Inspect') + ' 🔎'),
				]),
				E('textarea', {
					'id': 'container-file-text',
					'readonly': true,
					'rows': '5',
					'style': 'width: 100%; font-family: monospace; font-size: 12px; padding: 10px; border: 1px solid #ccc;'
				}, '')
			]);

			return fileDiv;
		}, this);


		// INSPECT TAB
		s.tab('inspect', _('Inspect'));
		let inspectDiv = null;

		o = s.taboption('inspect', form.Button, 'json', _('Container Inspect'));
		o.render = L.bind(() => {
			if (inspectDiv) {
				return inspectDiv;
			}

			inspectDiv = E('div', { 'class': 'cbi-section' }, [
				E('div', { 'style': 'margin-bottom: 10px;' }, [
					E('button', {
						'class': 'cbi-button cbi-button-neutral',
						'style': 'margin-left: 5px;',
						'click': () => dm2.container_inspect({ id: this_container.Id }).then(response => {
							const output = document.getElementById('container-inspect-output');
							output.textContent = JSON.stringify(response.body, null, 2);
							return;
						}),
					}, _('Inspect') + ' 🔎'),
				]),
			]);

			return inspectDiv;
		}, this);

		o = s.taboption('inspect', form.DummyValue, 'json');
		o.cfgvalue = () => E('pre', { style: 'overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;',
			id: 'container-inspect-output' }, 
			JSON.stringify(this_container, null, 2));


		// TERMINAL TAB
		s.tab('console', _('Console'));

		o = s.taboption('console', form.DummyValue, 'console_controls', _('Console Connection'));
		o.render = L.bind(() => {
			const status = this.getContainerStatus(this_container);
			const isRunning = status === 'running';

			if (!isRunning) {
				return E('div', { 'class': 'alert-message warning' },
					_('Container is not running. Cannot connect to console.'));
			}

			const consoleDiv = E('div', { 'class': 'cbi-section' }, [
				E('div', { 'style': 'margin-bottom: 15px;' }, [
					E('label', { 'style': 'margin-right: 10px;' }, _('Command:')),
					E('span', { 'id': 'console-command-wrapper' }, [
						new ui.Combobox('/bin/sh', [
							'/bin/ash',
							'/bin/bash',
						], {id: 'console-command' }).render()
					]),
					E('label', { 'style': 'margin-right: 10px; margin-left: 20px;' }, _('User(-u)')),
					E('input', {
						'type': 'text',
						'id': 'console-uid',
						'class': 'cbi-input-text',
						'placeholder': 'e.g., root or user id',
						'style': 'width: 150px; margin-right: 10px;'
					}),
					E('label', { 'style': 'margin-right: 10px; margin-left: 20px;' }, _('Port:')),
					E('input', {
						'type': 'number',
						'id': 'console-port',
						'class': 'cbi-input-text',
						'value': '7682',
						'min': '1024',
						'max': '65535',
						'style': 'width: 100px; margin-right: 10px;'
					}),
					E('button', {
						'class': 'cbi-button cbi-button-positive',
						'id': 'console-connect-btn',
						'click': () => this.connectConsole(this_container.Id)
					}, _('Connect')),
				]),
				E('div', {
					'id': 'console-frame-container',
					'style': 'display: none; margin-top: 15px;'
				}, [
					E('div', { 'style': 'margin-bottom: 10px;' }, [
						E('button', {
							'class': 'cbi-button cbi-button-negative',
							'click': () => this.disconnectConsole()
						}, _('Disconnect')),
						E('span', {
							'id': 'console-status',
							'style': 'margin-left: 10px; font-style: italic;'
						}, _('Connected to container console'))
					]),
					E('iframe', {
						'id': 'ttyd-frame',
						'class': 'xterm',
						'src': '',
						'style': 'width: 100%; height: 600px; border: 1px solid #ccc; border-radius: 3px;'
					})
				])
			]);

			return consoleDiv;
		}, this);

		// WEBSOCKET TAB
		s.tab('wsconsole', _('WebSocket'));

		dm2.js_api_ready.then(([apiAvailable, host]) => {
			// Wait for JS API availability check to complete
			// Check if JS API is available
			if (!apiAvailable) {
				return;
			}

			o = s.taboption('wsconsole', form.DummyValue, 'wsconsole_controls', _('WebSocket Console'));
			o.render = L.bind(function() {
				const status = this.getContainerStatus(this_container);
				const isRunning = status === 'running';

				if (!isRunning) {
					return E('div', { 'class': 'alert-message warning' },
						_('Container is not running. Cannot connect to WebSocket console.'));
				}
				const wsDiv = E('div', { 'class': 'cbi-section' }, [
					E('div', { 'style': 'margin-bottom: 10px;' }, [
						E('label', { 'style': 'margin-right: 10px;' }, _('Streams:')),
						E('label', { 'style': 'margin-right: 6px;' }, [
							E('input', { 'type': 'checkbox', 'id': 'ws-stdin', 'checked': 'checked', 'style': 'margin-right: 4px;' }),
							_('Stdin')
						]),
						E('label', { 'style': 'margin-right: 6px;' }, [
							E('input', { 'type': 'checkbox', 'id': 'ws-stdout', 'checked': 'checked', 'style': 'margin-right: 4px;' }),
							_('Stdout')
						]),
						E('label', { 'style': 'margin-right: 6px;' }, [
							E('input', { 'type': 'checkbox', 'id': 'ws-stderr', 'style': 'margin-right: 4px;' }),
							_('Stderr')
						]),
						E('label', { 'style': 'margin-right: 6px;' }, [
							E('input', { 'type': 'checkbox', 'id': 'ws-logs', 'style': 'margin-right: 4px;' }),
							_('Include logs')
						]),
						E('button', {
							'class': 'cbi-button cbi-button-positive',
							'id': 'ws-connect-btn',
							'click': () => this.connectWebsocketConsole()
						}, _('Connect')),
						E('button', {
							'class': 'cbi-button cbi-button-neutral',
							'click': () => this.disconnectWebsocketConsole(),
							'style': 'margin-left: 6px;'
						}, _('Disconnect')),
						E('span', { 'id': 'ws-console-status', 'style': 'margin-left: 10px; color: #666;' }, _('Disconnected')),
					]),
					E('div', {
						'id': 'ws-console-output',
						'style': 'height: 320px; border: 1px solid #ccc; border-radius: 3px; padding: 8px; background:#111; color:#0f0; font-family: monospace; overflow: auto; white-space: pre-wrap;'
					}, ''),
					E('div', { 'style': 'margin-top: 10px; display: flex; gap: 6px;' }, [
						E('textarea', {
							'id': 'ws-console-input',
							'rows': '3',
							'placeholder': _('Type command here... (Ctrl+D to detach)'),
							'style': 'flex: 1; padding: 6px; font-family: monospace; resize: vertical;',
							'keydown': (ev) => {
								if (ev.key === 'Enter' && !ev.shiftKey) {
									ev.preventDefault();
									this.sendWebsocketInput();
								} else if (ev.key === 'd' && ev.ctrlKey) {
									ev.preventDefault();
									this.sendWebsocketDetach();
								}
							}
						}),
						E('button', {
							'class': 'cbi-button cbi-button-positive',
							'click': () => this.sendWebsocketInput()
						}, _('Send'))
					])
				]);

				return wsDiv;
			}, this);
		});

		// LOGS TAB
		s.tab('logs', _('Logs'));
		let logsDiv = null;
		let logsLoaded = false;

		o = s.taboption('logs', form.DummyValue, 'log_controls', _('Log Controls'));
		o.render = L.bind(() => {
			if (logsDiv) {
				return logsDiv;
			}

			logsDiv = E('div', { 'class': 'cbi-section' }, [
				E('div', { 'style': 'margin-bottom: 10px;' }, [
					E('label', { 'style': 'margin-right: 10px;' }, _('Lines to show:')),
					E('input', {
						'type': 'number',
						'id': 'log-lines',
						'class': 'cbi-input-text',
						'value': '100',
						'min': '1',
						'style': 'width: 80px;'
					}),
					E('button', {
						'class': 'cbi-button cbi-button-positive',
						'style': 'margin-left: 10px;',
						'click': () => this.loadLogs(this_container.Id)
					}, _('Load Logs')),
					E('button', {
						'class': 'cbi-button cbi-button-neutral',
						'style': 'margin-left: 5px;',
						'click': () => this.clearLogs()
					}, _('Clear')),
				]),
				E('div', {
					'id': 'container-logs-text',
					'style': 'width: 100%; font-family: monospace; padding: 10px; border: 1px solid #ccc; overflow: auto;',
					'innerHTML': ''
				})
			]);

			return logsDiv;
		}, this);

		o = s.taboption('logs', form.DummyValue, 'log_display', _('Container Logs'));
		o.render = L.bind(() => {
			// Auto-load logs when tab is first accessed
			if (!logsLoaded) {
				logsLoaded = true;
				this.loadLogs();
			}
			return E('div');
		}, this);

		this.map = m;

		// Render the form and add buttons above it
		return m.render()
			.then(fe => {
				mainContainer.appendChild(fe);

				poll.add(L.bind(() => {
					if (this.getContainerStatus(this_container) !== 'running')
						return Promise.resolve();

					return dm2.container_changes({ id: this_container.Id })
						.then(L.bind(function(res) {
							if (res.code < 300 && Array.isArray(res.body)) {
								// Update changes table using L.ui.Table.update()
								if (this.changesTable) {
									const rows = res.body.map(change => change ? [
										ChangeTypes[change?.Kind] || change?.Kind,
										change?.Path
									] : []);
									this.changesTable.update(rows);
								}

								// Update the raw JSON field
								const changesField = document.getElementById('raw-changes-field');
								if (changesField) {
									changesField.textContent = JSON.stringify(res.body, null, 2);
								}
							}
						}, this))
						.catch(err => {
							console.error('Failed to poll container changes', err);
							return null;
						});
				}, this), 5);

				// Auto-refresh Stats table every 5 seconds (if container is running)
				poll.add(L.bind(() => {
					if (this.getContainerStatus(this_container) !== 'running')
						return Promise.resolve();

					return dm2.container_stats({ id: this_container.Id, query: { 'stream': false, 'one-shot': true } })
						.then(L.bind(function(res) {
							if (res.code < 300 && res.body) {
								return updateStats(res.body);
							}
						}, this))
						.catch(err => {
							console.error('Failed to poll container stats', err);
							return null;
						});
				}, this), 5);

				// Auto-refresh PS table every 5 seconds (if container is running)
				poll.add(L.bind(() => {
					if (this.getContainerStatus(this_container) !== 'running')
						return Promise.resolve();

					return dm2.container_top({ id: this_container.Id, query: { 'ps_args': this.psArgs || '-ww' } })
						.then(L.bind(function(res) {
							if (res.code < 300 && res.body && res.body.Processes) {
								// Check if titles changed - if so, rebuild the table
								if (res.body.Titles && JSON.stringify(res.body.Titles) !== JSON.stringify(this.psTitles)) {
									// Titles changed, need to recreate table
									this.psTitles = res.body.Titles;
									const psTableEl = document.getElementById('ps-table');
									if (psTableEl && psTableEl.parentNode) {
										const newTable = new L.ui.Table(
											res.body.Titles.map(t => _(t)),
											{ id: 'ps-table' },
											E('em', [_('No processes running')])
										);
										newTable.update(res.body.Processes);
										this.psTable = newTable;
										psTableEl.parentNode.replaceChild(newTable.render(), psTableEl);
									}
								} else if (this.psTable) {
									// Titles same, just update data
									this.psTable.update(res.body.Processes);
								}

								// Update raw JSON field
								const psField = document.getElementById('raw-ps-field');
								if (psField) {
									psField.textContent = JSON.stringify(res.body, null, 2);
								}
							}
						}, this))
						.catch(err => {
								console.error('Failed to poll container processes', err);
								return null;
							});
				}, this), 5);

				return mainContainer;
			});
	},

	handleSave(ev) {
		ev?.preventDefault();

		const map = this.map;
		if (!map)
			return Promise.reject(new Error(_('Form is not ready yet.')));

		// const listToKv = view.listToKv;

		const get = (opt) => map.data.get('json', 'cont', opt);
		// const getn = (opt) => map.data.get('json', 'nets', opt);
		const gethc = (opt) => map.data.get('json', 'hostcfg', opt);
		const toBool = (val) => (val === 1 || val === '1' || val === true);
		const toInt = (val) => val ? Number.parseInt(val) : undefined;
		// const toFloat = (val) => val ? Number.parseFloat(val) : undefined;

		// First: update properties
		map.parse()
			.then(() => {
				const this_container = map.data.get('json', 'cont');
				const id = this_container?.Id;
				const nc = gethc('NanoCpus');
				/* In the container edit context, there are not many items we
				can change - duplicate the container */
				const createBody = {

					CpuShares: toInt(gethc('CpuShares')),
					Memory: toInt(gethc('Memory')),
					MemorySwap: toInt(gethc('MemorySwap')),
					MemoryReservation: toInt(gethc('MemoryReservation')),
					BlkioWeight: toInt(gethc('BlkioWeight')),

					CpuPeriod: toInt(gethc('CpuPeriod')),
					CpuQuota: toInt(gethc('CpuQuota')),
					NanoCPUs: nc ? Math.round(nc * 1e9) : undefined, // unit: 10^-9, input: float
					OomKillDisable: toBool(gethc('OomKillDisable')),

					RestartPolicy: { Name: get('restart_policy') || this_container.HostConfig?.RestartPolicy?.Name },

				};

				return { id, createBody };
			})
			.then(({ id, createBody }) => dm2.container_update({ id: id, body: createBody}))
			.then((response) => {
				if (response?.code >= 300) {
					ui.addTimeLimitedNotification(_('Container update failed'), [response?.body?.message || _('Unknown error')], 7000, 'warning');
					return false;
				}

				const msgTitle = _('Updated');
				if (response?.body?.Warnings)
					ui.addTimeLimitedNotification(msgTitle + _(' with warnings'), [response?.body?.Warnings], 5000);
				else
					ui.addTimeLimitedNotification(msgTitle, [ _('OK') ], 4000, 'info');

				if (get('Name') === null)
					setTimeout(() => window.location.href = `${this.dockerman_url}/containers`, 1000);

				return true;
			})
			.catch((err) => {
				ui.addTimeLimitedNotification(_('Container update failed'), [err?.message || err], 7000, 'warning');
				return false;
			});

		// Then: update name (separate operation)
		return map.parse()
			.then(() => {
				const this_container = map.data.get('json', 'cont');
				const name = this_container.Name || get('Name');
				const id = this_container.Id || get('Id');

				return { id, name };
			})
			.then(({ id, name }) => dm2.container_rename({ id: id, query: { name: name } }))
			.then((response) => {
				this.handleDockerResponse(response, _('Container rename'), {
					showOutput: false,
					showSuccess: false
				});

				return setTimeout(() => window.location.href = `${this.dockerman_url}/containers`, 1000);
			})
			.catch((err) => {
				this.showNotification(_('Container rename failed'), err?.message || String(err), 7000, 'error');
				return false;
			});
	},

	connectWebsocketConsole() {
		const connectBtn = document.getElementById('ws-connect-btn');
		const statusEl = document.getElementById('ws-console-status');
		const outputEl = document.getElementById('ws-console-output');
		const view = this;

		if (connectBtn) connectBtn.disabled = true;
		if (statusEl) statusEl.textContent = _('Connecting…');

		// Clear the output buffer when connecting anew
		if (outputEl) outputEl.innerHTML = '';

		// Initialize input buffer
		this.consoleInputBuffer = '';

		// Tear down any previous hijack or websocket without user-facing noise
		if (this.hijackController) {
			try { this.hijackController.abort(); } catch (e) {}
			this.hijackController = null;
		}
		if (this.consoleWs) {
			try {
				this.consoleWs.onclose = null;
				this.consoleWs.onerror = null;
				this.consoleWs.onmessage = null;
				this.consoleWs.close();
			} catch (e) {}
			this.consoleWs = null;
		}

		const stdin = document.getElementById('ws-stdin')?.checked ? '1' : '0';
		const stdout = document.getElementById('ws-stdout')?.checked ? '1' : '0';
		const stderr = document.getElementById('ws-stderr')?.checked ? '1' : '0';
		const logs = document.getElementById('ws-logs')?.checked ? '1' : '0';
		const stream = '1';

		const params = {
			stdin: stdin,
			stdout: stdout,
			stderr: stderr,
			logs: logs,
			stream: stream,
			detachKeys: 'ctrl-d',
		}

		dm2.container_attach_ws({ id: this.container.Id, query: params })
		.then(response => {
			if (!response.ok) {
				throw new Error(`HTTP ${response.status}: ${response.statusText}`);
			}

			// Get the WebSocket connection
			const ws = response.ws || response.body;
			let opened = false;

			if (!ws || ws.readyState === undefined) {
				throw new Error('No WebSocket connection');
			}

			// Expect binary frames from Docker hijack; decode as UTF-8 text
			ws.binaryType = 'arraybuffer';

			// Set up WebSocket message handler
			ws.onmessage = (event) => {
				try {
					const renderAndAppend = (t) => {
						if (outputEl && t) {
							outputEl.innerHTML += dm2.ansiToHtml(t);
							outputEl.scrollTop = outputEl.scrollHeight;
						}
					};

					let text = '';
					const data = event.data;

					if (typeof data === 'string') {
						text = data;
					} else if (data instanceof ArrayBuffer) {
						text = new TextDecoder('utf-8').decode(new Uint8Array(data));
					} else if (data instanceof Blob) {
						// Fallback for Blob frames
						const reader = new FileReader();
						reader.onload = () => {
							const buf = reader.result;
							const t = new TextDecoder('utf-8').decode(new Uint8Array(buf));
							renderAndAppend(t);
						};
						reader.readAsArrayBuffer(data);
						return;
					}

					renderAndAppend(text);
				} catch (e) {
					console.error('Error processing message:', e);
				}
			};

			// Set up WebSocket error handler
			ws.onerror = (error) => {
				console.error('WebSocket error:', error);
				if (statusEl) statusEl.textContent = _('Error');
				view.showNotification(_('Error'), _('WebSocket error'), 7000, 'error');
				if (ws === view.consoleWs) {
					view.consoleWs = null;
				}
			};

			// Set up WebSocket close handler
			ws.onclose = (evt) => {
				if (!opened) return; // Suppress close noise from previous/failed sockets
				if (statusEl) statusEl.textContent = _('Disconnected');
				if (connectBtn) connectBtn.disabled = false;
				if (ws === view.consoleWs) {
					view.consoleWs = null;
				}
				const code = evt?.code;
				const reason = evt?.reason;
				view.showNotification(_('Info'), _('Console connection closed') + (code ? ` (code: ${code}${reason ? ', ' + reason : ''})` : ''), 3000, 'info');
			};

			ws.onopen = () => {
				opened = true;
				if (statusEl) statusEl.textContent = _('Connected');
				if (connectBtn) connectBtn.disabled = false;
				view.showNotification(_('Success'), _('Console connected'), 3000, 'info');

				// Store WebSocket reference so it doesn't get garbage collected
				view.consoleWs = ws;
			};

			// If already open (promise resolved after onopen), set state immediately
			if (ws.readyState === WebSocket.OPEN) {
				opened = true;
				view.consoleWs = ws;
				if (statusEl) statusEl.textContent = _('Connected');
				if (connectBtn) connectBtn.disabled = false;
			}
		})
		.catch(err => {
			if (err.name === 'AbortError') {
				if (statusEl) statusEl.textContent = _('Disconnected');
			} else {
				if (statusEl) statusEl.textContent = _('Error');
				view.showNotification(_('Error'), err?.message || String(err), 7000, 'error');
			}
			if (connectBtn) connectBtn.disabled = false;
			view.hijackController = null;
		});
	},

	disconnectWebsocketConsole() {
		const statusEl = document.getElementById('ws-console-status');
		const connectBtn = document.getElementById('ws-connect-btn');

		if (this.hijackController) {
			this.hijackController.abort();
			this.hijackController = null;
		}

		if (statusEl) statusEl.textContent = _('Disconnected');
		if (connectBtn) connectBtn.disabled = false;
		this.showNotification(_('Info'), _('Console disconnected'), 3000, 'info');
	},

	sendWebsocketInput() {
		const inputEl = document.getElementById('ws-console-input');
		if (!inputEl) return;

		const text = inputEl.value || '';

		// Check if WebSocket is actually connected
		if (this.consoleWs && this.consoleWs.readyState === WebSocket.OPEN) {
			try {
				const payload = text.endsWith('\n') ? text : `${text}\n`;
				this.consoleWs.send(payload);
				inputEl.value = '';
			} catch (e) {
				console.error('Error sending:', e);
				this.showNotification(_('Error'), _('Failed to send data'), 5000, 'error');
			}
		} else {
			this.showNotification(_('Error'), _('Console is not connected'), 5000, 'error');
		}
	},

	sendWebsocketDetach() {
		// Send ctrl-d (ASCII 4, EOT) to detach
		if (this.consoleWs && this.consoleWs.readyState === WebSocket.OPEN) {
			try {
				this.consoleWs.send('\x04');
				this.showNotification(_('Info'), _('Detach signal sent (Ctrl+D)'), 3000, 'info');
			} catch (e) {
				console.error('Error sending detach:', e);
				this.showNotification(_('Error'), _('Failed to send detach signal'), 5000, 'error');
			}
		} else {
			this.showNotification(_('Error'), _('Console is not connected'), 5000, 'error');
		}
	},

	handleFileUpload(container_id) {
		const path = document.getElementById('file-path')?.value || '/';

		const q_params = { path: encodeURIComponent(path) };

		return this.super('handleXHRTransfer', [{
			q_params: { query: q_params },
			method: 'PUT',
			commandCPath: `/container/archive/put/${container_id}/`,
			commandDPath: `/containers/${container_id}/archive`,
			commandTitle: _('Uploading…'),
			commandMessage: _('Uploading file to container…'),
			successMessage: _('File uploaded to') + ': ' + path,
			pathElementId: 'file-path',
			defaultPath: '/'
		}]);
	},

	handleFileDownload(container_id) {
		const path = document.getElementById('file-path')?.value || '/';
		const view = this;

		if (!path || path === '') {
			this.showNotification(_('Error'), _('Please specify a path'), 5000, 'error');
			return;
		}

		// Direct HTTP download bypassing RPC buffering
		window.location.href = `${this.dockerman_url}/container/archive/get/${container_id}` + `/?path=${encodeURIComponent(path)}`;
		return;
	},

	handleInfoArchive(container_id) {
		const path = document.getElementById('file-path')?.value || '/';
		const fileTextarea = document.getElementById('container-file-text');

		if (!fileTextarea) return;

		return dm2.container_info_archive({ id: container_id, query: { path: path } })
			.then((response) => {
				if (response?.code >= 300) {
					fileTextarea.value = _('Path error') + '\n' + (response?.body?.message || _('Unknown error'));
					this.showNotification(_('Error'), [response?.body?.message || _('Path error')], 7000, 'error');
					return false;
				}

				// check response?.headers?.entries?.length in case fetch API is used 
				if (!response.headers || response?.headers?.entries?.length == 0) return true;

				let fileInfo;
				try {
					fileInfo = JSON.parse(atob(response?.headers?.get?.('x-docker-container-path-stat') || response?.headers?.['x-docker-container-path-stat']));
					fileTextarea.value = 
						`name: ${fileInfo?.name}\n` +
						`size: ${fileInfo?.size}\n` +
						`mode: ${this.modeToRwx(fileInfo?.mode)}\n` +
						`mtime: ${fileInfo?.mtime}\n` +
						`linkTarget: ${fileInfo?.linkTarget}\n`;
				} catch {
					this.showNotification(_('Missing header or CORS interfering'), ['X-Docker-Container-Path-Stat'], 5000, 'notice');
				}

				return true;
			})
			.catch((err) => {
				const errorMsg = err?.message || String(err) || _('Path error');
				fileTextarea.value = _('Path error') + '\n' + errorMsg;
				this.showNotification(_('Error'), [errorMsg], 7000, 'error');
				return false;
			});
	},

	loadLogs(container_id) {
		const lines = parseInt(document.getElementById('log-lines')?.value || '100');
		const logsDiv = document.getElementById('container-logs-text');

		if (!logsDiv) return;

		logsDiv.innerHTML = '<em style="color: #999;">' + _('Loading logs…') + '</em>';

		return dm2.container_logs({ id: container_id, query: { tail: lines, stdout: true, stderr: true } })
			.then((response) => {
				if (response?.code >= 300) {
					logsDiv.innerHTML = '<span style="color: #ff5555;">' + _('Error loading logs:') + '</span><br/>' + 
						(response?.body?.message || _('Unknown error'));
					this.showNotification(_('Error'), response?.body?.message || _('Failed to load logs'), 7000, 'error');
					return false;
				}

				const logText = response?.body || _('No logs available');
				// Convert ANSI codes to HTML and set innerHTML
				logsDiv.innerHTML = dm2.ansiToHtml(logText);
				logsDiv.scrollTop = logsDiv.scrollHeight;
				return true;
			})
			.catch((err) => {
				const errorMsg = err?.message || String(err) || _('Failed to load logs');
				logsDiv.innerHTML = '<span style="color: #ff5555;">' + _('Error loading logs:') + '</span><br/>' + errorMsg;
				this.showNotification(_('Error'), errorMsg, 7000, 'error');
				return false;
			});
	},

	clearLogs() {
		const logsDiv = document.getElementById('container-logs-text');
		if (logsDiv) {
			logsDiv.innerHTML = '';
		}
	},

	connectConsole(container_id) {
		const commandWrapper = document.getElementById('console-command');
		const selectedItem = commandWrapper?.querySelector('li[selected]');
		const command = selectedItem?.textContent?.trim() || '/bin/sh';
		const uid = document.getElementById('console-uid')?.value || '';
		const port = parseInt(document.getElementById('console-port')?.value || '7682');
		const view = this;

		const connectBtn = document.getElementById('console-connect-btn');
		if (connectBtn) connectBtn.disabled = true;

		// Call RPC to start ttyd
		return dm2.container_ttyd_start({
			id: container_id,
			cmd: command,
			port: port,
			uid: uid
		})
		.then((response) => {
			if (connectBtn) connectBtn.disabled = false;

			if (response?.code >= 300) {
				const errorMsg = response?.body?.error || response?.body?.message || _('Failed to start console');
				view.showNotification(_('Error'), errorMsg, 7000, 'error');
				return false;
			}

			// Show iframe and set source
			const frameContainer = document.getElementById('console-frame-container');
			if (frameContainer) {
				frameContainer.style.display = 'block';
				const ttydFrame = document.getElementById('ttyd-frame');
				if (ttydFrame) {
					// Wait for ttyd to fully start and be ready for connections
					// Use a retry pattern to handle timing variations
					const waitForTtydReady = (attempt = 0, maxAttempts = 5, initialDelay = 500) => {
						const delay = initialDelay + (attempt * 200); // Increase delay on retries

						setTimeout(() => {
							const protocol = window.location.protocol === 'https:' ? 'https' : 'http';
							const ttydUrl = `${protocol}://${window.location.hostname}:${port}`;

							// Test connection with a simple HEAD request
							fetch(ttydUrl, { method: 'HEAD', mode: 'no-cors' })
								.then(() => {
									// Connection successful, load the iframe
									ttydFrame.src = ttydUrl;
								})
								.catch(() => {
									// Connection failed, retry if we haven't exceeded max attempts
									if (attempt < maxAttempts - 1) {
										waitForTtydReady(attempt + 1, maxAttempts, initialDelay);
									} else {
										// Max retries exceeded, load iframe anyway
										ttydFrame.src = ttydUrl;
										view.showNotification(_('Warning'), _('TTYd may still be starting up'), 5000, 'warning');
									}
								});
						}, delay);
					};

					waitForTtydReady();
				}
			}

			view.showNotification(_('Success'), _('Console connected'), 3000, 'info');
			return true;
		})
		.catch((err) => {
			if (connectBtn) connectBtn.disabled = false;
			const errorMsg = err?.message || String(err) || _('Failed to connect to console');
			view.showNotification(_('Error'), errorMsg, 7000, 'error');
			return false;
		});
	},

	disconnectConsole() {
		const frameContainer = document.getElementById('console-frame-container');
		if (frameContainer) {
			frameContainer.style.display = 'none';
			const ttydFrame = document.getElementById('ttyd-frame');
			if (ttydFrame) {
				ttydFrame.src = '';
			}
		}

		this.showNotification(_('Info'), _('Console disconnected'), 3000, 'info');
	},

	executeAction(ev, action, container_id) {
		ev?.preventDefault();

		const actionMap = Object.freeze({
			'start': _('Start'),
			'restart': _('Restart'),
			'stop': _('Stop'),
			'kill': _('Kill'),
			'pause': _('Pause'),
			'unpause': _('Unpause'),
			'remove': _('Remove'),
		});

		const actionLabel = actionMap[action] || action;

		// Confirm removal
		if (action === 'remove') {
			if (!confirm(_('Remove container?'))) {
				return;
			}
		}

		const view = this;
		const methodName = 'container_' + action;
		const method = dm2[methodName];

		if (!method) {
			view.showNotification(_('Error'), _('Action unavailable: ') + action, 7000, 'error');
			return;
		}

		view.executeDockerAction(
			method,
			{ id: container_id, query: {} },
			actionLabel,
			{
				showOutput: false,
				showSuccess: true,
				successMessage: actionLabel + _(' completed'),
				successDuration: 5000,
				onSuccess: () => {
					if (action === 'remove') {
						setTimeout(() => window.location.href = `${this.dockerman_url}/containers`, 1000);
					} else {
						setTimeout(() => location.reload(), 1000);
					}
				}
			}
		);
	},

	executeNetworkAction(action, networkID, networkName, this_container) {
		const view = this;

		if (action === 'disconnect') {
			if (!confirm(_('Disconnect network "%s" from container?').format(networkName))) {
				return;
			}

			view.executeDockerAction(
				dm2.network_disconnect,
				{
					id: networkID,
					body: { Container: this_container.Id, Force: false }
				},
				_('Disconnect network'),
				{
					showOutput: false,
					showSuccess: true,
					successMessage: _('Network disconnected'),
					successDuration: 5000,
					onSuccess: () => {
						setTimeout(() => location.reload(), 1000);
					}
				}
			);
		} else if (action === 'connect') {
			const newNetworks = this.networks.filter(n => !Object.keys(this_container.NetworkSettings?.Networks || {}).includes(n.Name));

			if (newNetworks.length === 0) {
				view.showNotification(_('Info'), _('No additional networks available to connect'), 5000, 'info');
				return;
			}

			// Create modal dialog for selecting network
			const networkSelect = E('select', { 
				'id': 'network-select',
				'class': 'cbi-input-select',
				'style': 'width:100%; margin-top:10px;'
			}, newNetworks.map(n => {
				const subnet0 = n?.IPAM?.Config?.[0]?.Subnet;
				const subnet1 = n?.IPAM?.Config?.[1]?.Subnet;
				return E('option', { 'value': n.Id }, [`${n.Name}${n?.Driver ? ' | ' + n.Driver : ''}${subnet0 ? ' | ' + subnet0 : ''}${subnet1 ? ' | ' + subnet1 : ''}`]);
			}));

			const ip4Input = E('input', {
				'type': 'text',
				'id': 'network-ip4',
				'class': 'cbi-input-text',
				'placeholder': 'e.g., 172.18.0.5',
				'style': 'width:100%; margin-top:5px;'
			});

			const ip6Input = E('input', {
				'type': 'text',
				'id': 'network-ip6',
				'class': 'cbi-input-text',
				'placeholder': 'e.g., 2001:db8:1::1',
				'style': 'width:100%; margin-top:5px;'
			});

			const aliasesInput = E('input', {
				'type': 'text',
				'id': 'network-aliases',
				'class': 'cbi-input-text',
				'placeholder': 'e.g., database,db (comma-separated)',
				'style': 'width:100%; margin-top:5px;'
			});

			const modalBody = E('div', { 'class': 'cbi-section' }, [
				E('p', {}, _('Select network to connect:')),
				networkSelect,
				E('label', { 'style': 'display:block; margin-top:10px;' }, _('IPv4 Address (optional):')),
				ip4Input,
				E('label', { 'style': 'display:block; margin-top:10px;' }, _('IPv6 Address (optional):')),
				ip6Input,
				E('label', { 'style': 'display:block; margin-top:10px;' }, _('Aliases (optional):')),
				aliasesInput,
			]);

			ui.showModal(_('Connect Network'), [
				modalBody,
				E('div', { 'class': 'right' }, [
					E('button', {
						'class': 'cbi-button cbi-button-neutral',
						'click': ui.hideModal
					}, _('Cancel')),
					' ',
					E('button', {
						'class': 'cbi-button cbi-button-positive',
						'click': () => {
							const selectedNetwork = networkSelect.value;
							const ip4Address = ip4Input.value || '';
							const ip6Address = ip6Input.value || '';
							const aliasesRaw = aliasesInput.value || '';
							const aliases = aliasesRaw.split(',').map(a => a.trim()).filter(Boolean);

							if (!selectedNetwork) {
								view.showNotification(_('Error'), [_('No network selected')], 5000, 'error');
								return;
							}

							ui.hideModal();

							const body = { Container: this_container.Id };
							body.EndpointConfig = { 
								IPAMConfig: { 
									IPv4Address: ip4Address || null, 
									IPv6Address: ip6Address || null 
								},
								Aliases: aliases.length > 0 ? aliases : null
							};

							view.executeDockerAction(
								dm2.network_connect,
								{ id: selectedNetwork, body: body },
								_('Connect network'),
								{
									showOutput: false,
									showSuccess: true,
									successMessage: _('Network connected'),
									successDuration: 5000,
									onSuccess: () => {
										setTimeout(() => location.reload(), 1000);
									}
								}
							);
						}
					}, _('Connect'))
				])
			]);
		}
	},

	// handleSave: null,
	handleSaveApply: null,
	handleReset: null,

});
