<%- include('top') %>
<script type="text/javascript">
	// Require login to see this page.
	app.auth.forceLogin()
</script>

<style type="text/css">
	label.form-label{
		font-weight: bold;
		margin-bottom: 1px;
	}

	div.form-group{
		margin-bottom: 1.1em;
	}

	.field-help{
		font-size: .82rem;
	}

	/* my Div class for my search bar */
	.search-wrapper {
		display: flex;
		gap: .5rem;
		align-items: center;
		margin-top: 10px;
	}

	/* Greys out a challenge/matching option that isn't available for the host. */
	.challengeType-container {
		pointer-events: none; /* Prevents clicking */
		opacity: 0.5;         /* Greys it out */
		filter: grayscale(1); /* Removes blue/color tint */
		cursor: not-allowed;
	}
</style>

<script type="text/javascript">

	// Parse the JSON object for a host to something the UI wants
	function hostParseRow(host) {
		host['created_on_text'] = moment(host['created_on'], "x").fromNow();
		host['updated_on_text'] = moment(host['updated_on'], "x").fromNow();
		host['wildcard_expires_text'] = moment(host['wildcard_expires'], "x").fromNow();
		host['targetssl_text'] = host['targetssl'] ? 'https://' : 'http://';
		host['forcessl_text'] = host['forcessl'] ? 'https://' : 'http://';
		host['wildcard_text'] = host['is_wildcard'] ? host['wildcard_status'] : 'Auto';
		host['wildcard_text'] = host['wildcard_parent'] ? 'Child' : host['wildcard_text'];
		host['wildcard_text_bg'] = 'warning';
		if(!host['is_wildcard']){
			host['wildcard_text_bg'] = 'success';
		}
		if(host['wildcard_status'] == 'Done'){
			host['wildcard_text_bg'] = 'success';
			host['wildcard_text'] = undefined;
		}
		if(host['wildcard_status'] && host['wildcard_status'].includes('failed')){
			host['wildcard_text_bg'] = 'danger';
		}

		return host;
	}

	function hostPopulate(onLoaded){
		app.api.get('host?detail=1&provider=1', function(error, res){
			if(error) return app.messages.action(error, $.scope.hosts.$this, 'danger');

			for(let host of res.results){
				$.scope.hosts.push(hostParseRow(host));
			}

			$.scope.hosts.put = function($el, item, list){
				$el.addClass('table-success');
				$el.fadeIn(2000, function(){
					$el.removeClass('table-success');
				});
			};

			if (onLoaded) onLoaded();
		});
	}

	// Mirror of utils/host_features.js stringify* helpers for populating the edit
	// form's textareas. The server re-parses the posted text authoritatively.
	function hostFeatureHeadersToText(obj){
		if(!obj || typeof obj != 'object') return '';
		return Object.keys(obj).map(function(name){ return name + ': ' + obj[name]; }).join('\n');
	}
	function hostFeatureListToText(arr){
		return Array.isArray(arr) ? arr.join('\n') : '';
	}

	// ----- Add / Edit modal --------------------------------------------------

	function hostModalClose(){ app.modal.close(); }

	// Built once; injected fresh into app.modal's body on every open() call
	// (unlike the old always-in-DOM static modal). .actionMessage lives
	// INSIDE the form (not a sibling, as it was before) because formAJAX's
	// error/success target resolves via $form.closest('div.card') (which
	// app.modal's plain, card-less .modal-content never matches) falling
	// back to $form.find('.actionMessage') -- a descendant search.
	var hostModalBodyHtml = `
		<form id="hostForm" method="POST" action="host" onsubmit="formAJAX(this)" evalAJAX="hostModalClose()">
			<div class="actionMessage m-0" style="display:none"></div>

			<ul class="nav nav-tabs flex-nowrap overflow-x-auto" role="tablist">
				<li class="nav-item"><button class="nav-link active" id="hostTab-general-btn" data-bs-toggle="tab" data-bs-target="#hostTab-general" type="button" role="tab">General</button></li>
				<li class="nav-item"><button class="nav-link" id="hostTab-tls-btn" data-bs-toggle="tab" data-bs-target="#hostTab-tls" type="button" role="tab">TLS &amp; Wildcard</button></li>
				<li class="nav-item"><button class="nav-link" id="hostTab-traffic-btn" data-bs-toggle="tab" data-bs-target="#hostTab-traffic" type="button" role="tab">Traffic</button></li>
				<li class="nav-item"><button class="nav-link" id="hostTab-headers-btn" data-bs-toggle="tab" data-bs-target="#hostTab-headers" type="button" role="tab">Headers</button></li>
				<li class="nav-item"><button class="nav-link" id="hostTab-access-btn" data-bs-toggle="tab" data-bs-target="#hostTab-access" type="button" role="tab">Access</button></li>
				<li class="nav-item"><button class="nav-link" id="hostTab-auth-btn" data-bs-toggle="tab" data-bs-target="#hostTab-auth" type="button" role="tab">Authentication</button></li>
			</ul>

			<div class="tab-content pt-3">

				<!-- General -->
				<div class="tab-pane fade show active" id="hostTab-general" role="tabpanel">
					<div class="form-group">
						<label for="host" class="form-label">Incoming host name</label>
						<input type="text" name="host" class="form-control" placeholder="ex: app.example.com, *.example.com, **.example.com, or **" validate="host">
						<b class="invalid-feedback"></b>
						<small class="field-help text-muted d-block">
							The public hostname clients request. Use <code>*.example.com</code>
							for one subdomain level, <code>**.example.com</code> for any depth,
							or <code>**</code> as a catch-all.
						</small>
						<small id="host-rename-help" class="field-help text-muted d-block" style="display:none">
							Wildcard hosts, their children, and auto-created subdomain cache
							entries can't be renamed here — the name is referenced elsewhere
							(the wildcard's own children, or the cache entry's parent). Delete
							and recreate instead.
						</small>
					</div>

					<div class="form-group">
						<label class="form-label">Incoming SSL</label>
						<div class="radio"><label>
							<input type="radio" name="forcessl" id="forcessl-true" value="true" checked>
							Force HTTPS <b>(recommended)</b>
						</label></div>
						<div class="radio"><label>
							<input type="radio" name="forcessl" id="forcessl-false" value="false">
							Allow both HTTP and HTTPS
						</label></div>
						<small class="field-help text-muted d-block">Redirect plain HTTP requests to HTTPS.</small>
					</div>

					<hr>

					<div class="form-group">
						<label for="ip" class="form-label">Target IP or host name</label>
						<input type="text" name="ip" class="form-control" placeholder="ex: 10.10.10.10, app.internal.net, or sso-manager" validate="target:3" />
						<b class="invalid-feedback"></b>
						<small class="field-help text-muted d-block">Where matching requests are proxied. Hostname or IP only &mdash; no protocol, port, or path.</small>
					</div>

					<div class="row">
						<div class="col form-group">
							<label for="targetPort" class="form-label">Target TCP port</label>
							<input type="number" name="targetPort" class="form-control" value="80" min="0" max="65535" />
							<b class="invalid-feedback"></b>
						</div>
						<div class="col form-group">
							<label class="form-label">Target SSL</label>
							<div class="radio"><label>
								<input type="radio" name="targetssl" id="targetssl-false" value="false" checked>
								Proxy to HTTP <b>(recommended)</b>
							</label></div>
							<div class="radio"><label>
								<input type="radio" name="targetssl" id="targetssl-true" value="true">
								Proxy to HTTPS
							</label></div>
							<small class="field-help text-muted d-block">Whether the proxy talks to the target over HTTP or HTTPS. Independent of Incoming SSL above — clients can use HTTPS to reach the proxy while it still talks plain HTTP to the target, or vice versa.</small>
						</div>
					</div>

					<hr>

					<div class="form-group">
						<label for="targets" class="form-label">Additional Targets (Load Balancing)</label>
						<textarea name="targets" class="form-control" rows="2" placeholder="10.0.0.2:8080&#10;10.0.0.3:8080"></textarea>
						<small class="field-help text-muted d-block">Add additional targets here (IP:port, one per line) to load balance across them using round-robin. The primary target above is always included.</small>
					</div>
				</div>

				<!-- TLS & Wildcard -->
				<div class="tab-pane fade" id="hostTab-tls" role="tabpanel">
					<div class="form-group autoSll">
						<label class="form-label">
							SSL <a href="https://letsencrypt.org/docs/challenge-types/" target="_blank">validation type</a>
						</label>
						<div class="radio" id="challengeType-HTTP-01-container"><label>
							<input type="radio" name="challengeType" id="challengeType-HTTP-01" value="HTTP-01" checked>
							HTTP-01
						</label></div>
						<div class="radio challengeType-container" id="challengeType-DNS-01-wildcard-container"><label>
							<input type="radio" name="challengeType" id="challengeType-DNS-01-wildcard" value="DNS-01-wildcard">
							DNS-01 Wildcard
						</label></div>
						<div class="radio challengeType-container" id="challengeType-child-container"><label>
							<input type="radio" name="challengeType" id="challengeType-wildcardChild" value="wildcardChild">
							Parent Wildcard from <i id="challengeType-child-relatedHost"></i>
						</label></div>
						<small class="field-help text-muted d-block">
							Options light up based on the host name: wildcard certs need a DNS
							provider for the domain; child hosts reuse a parent wildcard.
						</small>
					</div>

					<div class="form-group challengeType-container" id="wildcard_matchAny-container">
						<label class="form-label">Wildcard matching</label>
						<div class="radio"><label>
							<input type="radio" name="wildcard_matchAny" id="wildcard_matchAny-false" value="false" checked>
							Match only subdomains defined here <b>(recommended)</b>
						</label></div>
						<div class="radio"><label>
							<input type="radio" name="wildcard_matchAny" id="wildcard_matchAny-true" value="true">
							Match any subdomain and proxy to this host
						</label></div>
						<small class="field-help text-muted d-block">
							"Recommended" only routes subdomains you've explicitly registered
							as their own host (optionally as a "Parent Wildcard" child of this
							one, to reuse this cert). "Match any" auto-creates a temporary
							route to this host's target for <i>any</i> undefined subdomain the
							first time it's requested — convenient, but it means every subdomain
							typo or scan attempt also gets routed here.
						</small>
					</div>
				</div>

				<!-- Traffic -->
				<div class="tab-pane fade" id="hostTab-traffic" role="tabpanel">
					<div class="form-group">
						<label class="form-label">Rate limiting</label>
						<div class="radio"><label>
							<input type="radio" name="ratelimit_enabled" id="ratelimit_enabled-false" value="false" checked>
							Off <b>(recommended)</b>
						</label></div>
						<div class="radio"><label>
							<input type="radio" name="ratelimit_enabled" id="ratelimit_enabled-true" value="true">
							Limit requests per client IP
						</label></div>
					</div>
					<div class="row">
						<div class="col form-group">
							<label for="ratelimit_rate" class="form-label">Requests / sec</label>
							<input type="number" name="ratelimit_rate" class="form-control" value="10" min="1" max="1000000" />
						</div>
						<div class="col form-group">
							<label for="ratelimit_burst" class="form-label">Burst</label>
							<input type="number" name="ratelimit_burst" class="form-control" value="20" min="0" max="1000000" />
						</div>
					</div>
					<small class="field-help text-muted d-block mb-3">Token bucket per client IP; bursts above the rate are queued, then rejected with 429.</small>

					<hr>

					<div class="form-group">
						<label class="form-label">Response caching</label>
						<div class="radio"><label>
							<input type="radio" name="respcache_enabled" id="respcache_enabled-false" value="false" checked>
							Off <b>(recommended)</b>
						</label></div>
						<div class="radio"><label>
							<input type="radio" name="respcache_enabled" id="respcache_enabled-true" value="true">
							Cache cacheable responses
						</label></div>
						<small class="field-help text-muted d-block">Cache upstream responses that declare themselves cacheable.</small>
					</div>

					<div class="form-group">
						<label class="form-label">HSTS</label>
						<div class="radio"><label>
							<input type="radio" name="hsts_enabled" id="hsts_enabled-false" value="false" checked>
							Off
						</label></div>
						<div class="radio"><label>
							<input type="radio" name="hsts_enabled" id="hsts_enabled-true" value="true">
							Send Strict-Transport-Security
						</label></div>
						<small class="field-help text-muted d-block">Tells browsers to only use HTTPS for this host. Enable once HTTPS is confirmed working.</small>
					</div>
				</div>

				<!-- Headers -->
				<div class="tab-pane fade" id="hostTab-headers" role="tabpanel">
					<div class="form-group">
						<label for="req_headers" class="form-label">Upstream request headers</label>
						<textarea name="req_headers" class="form-control" rows="3" placeholder="Name: value, one per line"></textarea>
						<small class="field-help text-muted d-block">Added to each request sent to the target. One <code>Name: value</code> per line.</small>
					</div>
					<div class="form-group">
						<label for="resp_headers" class="form-label">Response headers</label>
						<textarea name="resp_headers" class="form-control" rows="3" placeholder="Name: value, one per line"></textarea>
						<small class="field-help text-muted d-block">Added to each response returned to the client.</small>
					</div>
				</div>

				<!-- Access -->
				<div class="tab-pane fade" id="hostTab-access" role="tabpanel">
					<h6 class="text-muted">IP access</h6>
					<div class="form-group">
						<label for="ip_allow" class="form-label">Allow IPs / CIDRs</label>
						<textarea name="ip_allow" class="form-control" rows="2" placeholder="one per line; if set, only these are allowed"></textarea>
						<small class="field-help text-muted d-block">If non-empty, only these sources may connect (default-deny).</small>
					</div>
					<div class="form-group">
						<label for="ip_deny" class="form-label">Deny IPs / CIDRs</label>
						<textarea name="ip_deny" class="form-control" rows="2" placeholder="one per line; these are blocked"></textarea>
						<small class="field-help text-muted d-block">These sources are always blocked (deny wins over allow).</small>
					</div>
				</div>

				<!-- Authentication -->
				<div class="tab-pane fade" id="hostTab-auth" role="tabpanel">
					<p class="field-help text-muted">
						Pick one authentication method for this host &mdash; basic auth and
						SSO can't both be enabled, to avoid ambiguity about which one
						actually protected a request. Choose "Off" for a public host.
					</p>

					<div class="form-group">
						<div class="radio"><label>
							<input type="radio" name="auth_mode" id="auth_mode-none" value="none" checked onchange="hostAuthModeChanged('none')">
							Off (public)
						</label></div>
						<div class="radio"><label>
							<input type="radio" name="auth_mode" id="auth_mode-basic" value="basic" onchange="hostAuthModeChanged('basic')">
							Basic authentication
						</label></div>
						<div class="radio"><label>
							<input type="radio" name="auth_mode" id="auth_mode-sso" value="sso" onchange="hostAuthModeChanged('sso')">
							Single sign-on (SSO)
						</label></div>
					</div>
					<!-- Actually-submitted fields; kept in sync with the radios above by
					     hostAuthModeChanged() so only one can be true at a time. -->
					<input type="hidden" name="basicauth_enabled" id="basicauth_enabled-hidden" value="false">
					<input type="hidden" name="sso_enabled" id="sso_enabled-hidden" value="false">

					<div id="hostTab-auth-basicFields" style="display:none">
						<hr>
						<h6 class="text-muted">Basic authentication</h6>
						<div class="form-group">
							<label for="basicauth_realm" class="form-label">Realm</label>
							<input type="text" name="basicauth_realm" class="form-control" value="Restricted" placeholder="Restricted" />
						</div>
						<div class="form-group">
							<label for="basicauth_users" class="form-label">Users</label>
							<textarea name="basicauth_users" class="form-control" rows="2" placeholder="username:password, one per line"></textarea>
							<small class="field-help text-muted d-block">
								Current: <span class="basicauth-current">none</span>.
								Passwords are stored hashed and never shown here. Leave blank to keep
								the current users; entering any lines replaces the whole list. To
								manage individual users (delete / change password), use the table
								below once the host has been saved.
							</small>
						</div>
					</div>

					<div id="hostTab-auth-ssoFields" style="display:none">
						<hr>
						<h6 class="text-muted">Single sign-on (SSO)</h6>
						<p class="field-help text-muted">Gates the site behind the same identity provider the admin app uses. Empty allow-lists below mean any authenticated user is allowed.</p>

						<div class="form-group">
							<label for="sso_allow_users" class="form-label">Allowed users</label>
							<div class="input-group mb-1">
								<input type="text" class="form-control" list="hostSsoUsers" placeholder="type to search users…"
									onkeydown="if(event.key==='Enter'){event.preventDefault();allowListAdd(this,'sso_allow_users');}">
								<button type="button" class="btn btn-outline-secondary" onclick="allowListAdd(this.previousElementSibling,'sso_allow_users')">
									<i class="fa-solid fa-plus"></i> Add
								</button>
							</div>
							<textarea name="sso_allow_users" class="form-control" rows="2" placeholder="one email/username per line; blank = any authenticated user"></textarea>
						</div>
						<div class="form-group">
							<label for="sso_allow_groups" class="form-label">Allowed groups</label>
							<div class="input-group mb-1">
								<input type="text" class="form-control" list="hostSsoGroups" placeholder="type to search groups…"
									onkeydown="if(event.key==='Enter'){event.preventDefault();allowListAdd(this,'sso_allow_groups');}">
								<button type="button" class="btn btn-outline-secondary" onclick="allowListAdd(this.previousElementSibling,'sso_allow_groups')">
									<i class="fa-solid fa-plus"></i> Add
								</button>
							</div>
							<textarea name="sso_allow_groups" class="form-control" rows="2" placeholder="one group per line; blank = any authenticated user"></textarea>
						</div>
					</div>

					<div id="hostTab-auth-basicUsersMgmt" style="display:none">
						<hr>
						<h6 class="text-muted">Manage basic-auth users</h6>
						<div class="table-responsive">
							<table class="table table-sm">
								<thead><tr><th>Username</th><th>New password</th><th></th></tr></thead>
								<tbody id="basicAuthUserRows"></tbody>
							</table>
						</div>
					</div>
				</div>

			</div>
			<datalist id="hostSsoUsers"></datalist>
			<datalist id="hostSsoGroups"></datalist>
		</form>
	`;

	function openHostModal(title, submitLabel, host){
		app.modal.open({
			title: title,
			size: 'lg',
			bodyHtml: hostModalBodyHtml,
			footer: {
				// created_on/updated_on come back as redis-hash strings, not
				// numbers -- moment's bare fallback parser mishandles that
				// (silently "Invalid date"), same reason hostParseRow above
				// uses the explicit "x" (unix ms) format token.
				metaHtml: host ? app.modal.formatAudit(host, {formatDate: function(ms){ return moment(ms, 'x').format('YYYY-MM-DD HH:mm'); }}) : '',
				buttonsHtml: '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><i class="fa-solid fa-ban"></i> Cancel</button>'
					+ '<button type="submit" form="hostForm" class="btn btn-success"><i class="fa-solid fa-floppy-disk"></i> ' + $('<div>').text(submitLabel).html() + '</button>',
			},
			url: host ? {path: '/hosts/' + encodeURIComponent(host.host)} : null,
		});
		// The datalists were just rebuilt empty -- refill them (cheap re-fetch,
		// no cache bug here since this function has no cache guard to skip it).
		hostLoadAuthSuggestions();
	}

	function hostShowTab(id){
		bootstrap.Tab.getOrCreateInstance(document.getElementById(id)).show();
	}

	// Append a picked/typed value to one of the SSO allow-list textareas (deduped).
	function allowListAdd(input, name){
		let val = (input.value || '').trim();
		if(!val) return;
		let $ta = $('#hostForm textarea[name="' + name + '"]');
		let lines = ($ta.val() || '').split(/\r?\n/).map(s => s.trim()).filter(Boolean);
		if(lines.indexOf(val) === -1) lines.push(val);
		$ta.val(lines.join('\n'));
		input.value = '';
		input.focus();
	}

	// Host name of the record currently open in the edit modal, or null when
	// adding a new host (basic-auth user management needs a saved host to
	// attach users to).
	let hostFormCurrentHost = null;

	// The auth_mode radios share a name so the browser enforces mutual
	// exclusivity, but auth_mode itself isn't in Host's _keyMap -- the model
	// layer strips unrecognized fields on save (see model-redis's
	// processKeys), so it's never actually persisted. This keeps the two
	// hidden basicauth_enabled/sso_enabled inputs -- the real, submitted
	// fields -- in sync with whichever radio is selected, and shows/hides
	// the matching field group.
	function hostAuthModeChanged(mode){
		$('#basicauth_enabled-hidden').val(mode === 'basic' ? 'true' : 'false');
		$('#sso_enabled-hidden').val(mode === 'sso' ? 'true' : 'false');
		$('#hostTab-auth-basicFields').toggle(mode === 'basic');
		$('#hostTab-auth-ssoFields').toggle(mode === 'sso');
		$('#hostTab-auth-basicUsersMgmt').toggle(mode === 'basic' && !!hostFormCurrentHost);
	}

	// Per-user basic-auth management (delete / change password) for the host
	// currently open in the edit modal. Only shown once a host exists to attach
	// users to (not on "Add host", before it's been saved).
	function hostRenderBasicAuthUsers(host, users){
		let $rows = $('#basicAuthUserRows').empty();
		let usernames = Object.keys(users || {});
		if(!usernames.length){
			$rows.append('<tr><td colspan="3" class="text-muted">No basic-auth users yet.</td></tr>');
			return;
		}
		for(let username of usernames){
			let $tr = $('<tr>');
			$tr.append($('<td>').text(username));
			let $pass = $('<input type="text" class="form-control form-control-sm" placeholder="new password">');
			$tr.append($('<td>').append($pass));
			let $actions = $('<td>');
			let $save = $('<button type="button" class="btn btn-sm btn-outline-secondary me-1"><i class="fa-solid fa-key"></i></button>');
			$save.on('click', function(){
				let password = $pass.val();
				if(!password) return;
				app.api.put('host/' + encodeURIComponent(host) + '/basicauth-user/' + encodeURIComponent(username), {password}, function(error, data){
					if(error) return app.messages.action((data && data.message) || 'Failed to update password', $rows, 'danger');
					$pass.val('');
					app.messages.action('Password updated for "' + username + '".', $rows, 'success');
				});
			});
			// No confirm step, matching this form's existing "Delete" button
			// (host deletion itself is also a single click, no dialog — see the
			// host row actions above).
			let $del = $('<button type="button" class="btn btn-sm btn-outline-danger"><i class="fa-solid fa-trash"></i></button>');
			$del.on('click', function(){
				app.api.delete('host/' + encodeURIComponent(host) + '/basicauth-user/' + encodeURIComponent(username), function(error, data){
					if(error) return app.messages.action((data && data.message) || 'Failed to delete user', $rows, 'danger');
					$tr.remove();
					$('.basicauth-current').text(Object.keys((data && data.basicauth_users) || {}).join(', ') || 'none');
				});
			});
			$actions.append($save).append($del);
			$tr.append($actions);
			$rows.append($tr);
		}
	}

	// Fill the user/group datalists that back the allow-list autocomplete.
	function hostLoadAuthSuggestions(){
		app.api.get('host/auth-suggestions', function(error, data){
			if(error || !data) return;
			let $u = $('#hostSsoUsers').empty();
			for(let u of (data.users || [])) $u.append($('<option>').val(u));
			let $g = $('#hostSsoGroups').empty();
			for(let g of (data.groups || [])) $g.append($('<option>').val(g));
		});
	}

	// Return the form to a clean "add" state.
	function hostFormReset(){
		let form = document.getElementById('hostForm');
		form.reset();
		let $f = $(form);
		$f.attr('method', 'POST').attr('action', 'host').attr('evalAJAX', 'hostModalClose()');
		$f.find('[name=host]').prop('disabled', false);
		$('#host-rename-help').hide();
		if($f.validateClear) $f.validateClear();

		// A fresh host only qualifies for HTTP-01 until the name says otherwise.
		$('#challengeType-child-container, #challengeType-DNS-01-wildcard-container, #wildcard_matchAny-container')
			.addClass('challengeType-container');
		$('#challengeType-child-relatedHost').text('');
		$('.basicauth-current').text('none');
		hostFormCurrentHost = null;
		hostAuthModeChanged('none');
		hostShowTab('hostTab-general-btn');
	}

	function hostAddOpen(){
		openHostModal('Add host', 'Add host', null);
		hostFormReset();
	}

	async function hostEditOpen(host){
		let h = $.scope.hosts.getByKey(host);
		openHostModal('Edit ' + host, 'Save changes', h);
		hostFormReset();
		let $f = $('#hostForm');

		$f.attr('method', 'PUT').attr('action', 'host/' + encodeURIComponent(host));

		// Scalar fields: booleans drive the matching radio, everything else the
		// input with that name. Object/array fields are handled as text below.
		$.each(h, function(key, value){
			if(typeof value === 'boolean'){
				$f.find('#' + key + '-' + value).prop('checked', true);
			}else{
				$f.find("input[name='" + key + "']").val(value);
			}
		});

		$f.find("textarea[name='req_headers']").val(hostFeatureHeadersToText(h.req_headers));
		$f.find("textarea[name='targets']").val(hostFeatureListToText(h.targets));
		$f.find("textarea[name='resp_headers']").val(hostFeatureHeadersToText(h.resp_headers));
		$f.find("textarea[name='ip_allow']").val(hostFeatureListToText(h.ip_allow));
		$f.find("textarea[name='ip_deny']").val(hostFeatureListToText(h.ip_deny));
		$f.find("textarea[name='sso_allow_users']").val(hostFeatureListToText(h.sso_allow_users));
		$f.find("textarea[name='sso_allow_groups']").val(hostFeatureListToText(h.sso_allow_groups));

		// Never echo basic-auth passwords; show current usernames as a hint.
		$f.find("textarea[name='basicauth_users']").val('');
		$('.basicauth-current').text(Object.keys(h.basicauth_users || {}).join(', ') || 'none');

		// Auth: one radio drives both mutually-exclusive booleans.
		hostFormCurrentHost = host;
		let authMode = h.sso_enabled ? 'sso' : (h.basicauth_enabled ? 'basic' : 'none');
		$f.find('#auth_mode-' + authMode).prop('checked', true);
		hostAuthModeChanged(authMode);
		hostRenderBasicAuthUsers(host, h.basicauth_users);

		// The host name is the Redis record's key -- renaming it is a real
		// migration (see Host.prototype.update() in models/host.js), scoped
		// there to plain hosts only: a wildcard's children reference it by
		// name (wildcard_parent) and a cache entry's parent likewise, so
		// renaming either would orphan those pointers. Keep the field locked
		// for those cases; a plain host can be renamed freely.
		let hostRenameable = !h.is_wildcard && !h.wildcard_parent && !h.is_cache;
		$f.find('[name=host]').prop('disabled', !hostRenameable);
		$('#host-rename-help').toggle(!hostRenameable);

		// Reflect + enable the challenge-type options actually available for
		// this host. Setting the host field's .val() above does not fire a
		// 'keyup' event, so without this the "Parent Wildcard" option stayed
		// permanently greyed out on edit even when a valid parent wildcard
		// existed -- it only ever got un-greyed by the user re-typing the
		// hostname (the keyup handler further down).
		$('#challengeType-child-container, #challengeType-DNS-01-wildcard-container, #wildcard_matchAny-container')
			.addClass('challengeType-container');

		if(h.is_wildcard){
			$('#challengeType-DNS-01-wildcard-container').removeClass('challengeType-container');
			$('#challengeType-DNS-01-wildcard').prop('checked', true);
			$('#wildcard_matchAny-container').removeClass('challengeType-container');
		}else{
			let wildcardParent = await hostMatchWildcard(h.host);
			if(wildcardParent){
				$('#challengeType-child-container').removeClass('challengeType-container');
				$('#challengeType-child-relatedHost').text(wildcardParent.host);
			}
			if(h.wildcard_parent){
				$('#challengeType-wildcardChild').prop('checked', true);
			}else{
				$('#challengeType-HTTP-01').prop('checked', true);
			}
		}
	}

	function hostDownloadCert(host, type){
		app.host.getCert({host}, function(error, data){
			if(error) app.messages.action(error.message, $.scope.hosts.$this, 'danger');
			app.util.downloadFile(`${host}-${type}.crt`, data[type])
		});
	}

	function hostSearchInput(){
		let inputValue = $(event.target).val().toLowerCase();
		for(let hostObj of $.scope.hosts){
			if (hostObj.host.toLowerCase().includes(inputValue)) {
				hostObj.__jq_$el.show();
			} else {
				hostObj.__jq_$el.hide();
			}
		}
	};

	async function verifyWildcardRequirements(host){
		try{
			let res = await app.api.get(`dns/domain/${host}`);
			return res.results.length === 1;
		}catch(error){
			return false;
		}
	}

	function hostClearCache(btn){
		let $btn = $(btn);
		$btn.prop('disabled', true);
		app.host.clearCache(function(error, data){
			$btn.prop('disabled', false);
			if(error){
				return app.messages.action(error.message || error, $.scope.hosts.$this, 'danger');
			}
			app.messages.action(data.message, $.scope.hosts.$this, 'success');
		});
	}

	async function hostMatchWildcard(host){
		try{
			// Not /host/lookup/ -- that resolves an ALREADY-EXISTING host to its
			// own record, not a sibling wildcard (see the route's comment). This
			// dedicated endpoint correctly finds a usable wildcard parent whether
			// @host is brand new or already exists as its own host.
			let res = await app.api.get(`host/wildcard-parent/${host}`);
			return res.results || false;
		}catch(error){
			return false;
		}
	}

	// Determine what Let's Encrypt challenge type the given host name can use.
	// Delegated (not a direct bind against a captured selector) since
	// app.modal.open() rebuilds #hostForm from scratch on every call -- a
	// direct .on('keyup', ...) bind here would silently stop firing after
	// the first Add/Edit.
	app.modal.on('keyup', '#hostForm [name=host]', async function(){
		let $hostField = $(this);
		// Reset the allowed types on start
		$('#challengeType-child-container').addClass('challengeType-container');
		$('#challengeType-DNS-01-wildcard-container').addClass('challengeType-container');
		$('#wildcard_matchAny-container').addClass('challengeType-container');

		let host = $hostField.val();

		// If it's a wildcard, we must check the domain has a registered provider.
		if(host.startsWith("*.") && await verifyWildcardRequirements(host)){
			$('#challengeType-DNS-01-wildcard-container').removeClass('challengeType-container');
			$('#wildcard_matchAny-container').removeClass('challengeType-container');
			return;
		}

		// Check if a wildcard cert is available for the given host. When it is,
		// make "Parent Wildcard" the default choice (it reuses an existing cert).
		let wildcardParent = await hostMatchWildcard(host);
		if(wildcardParent){
			$('#challengeType-child-container').removeClass('challengeType-container');
			$('#challengeType-child-relatedHost').text(wildcardParent.host);
			$('#challengeType-wildcardChild').prop('checked', true);
			return;
		}

		// Revert the form to a valid state.
		$('#challengeType-child-relatedHost').text('');
		$('#challengeType-HTTP-01').prop('checked', true);
	});

	$(document).ready(function(){
		// Populate the host UI table, then check for a deep-linked host
		// (e.g. a direct load of /hosts/some-host, or app.modal's url option
		// having pushed that path) once the table's data is actually loaded.
		hostPopulate(function(){
			const slug = app.modal.deepLinkSlug('/hosts');
			if (slug) {
				const h = $.scope.hosts.getByKey(decodeURIComponent(slug));
				if (h) hostEditOpen(h.host);
			}
		});

		$.scope.hosts.take = function($el, item, list){
			$el.addClass('table-danger');
			$el.fadeOut(500, function(){ $el.remove() });
		};

		$.scope.hosts.putUpdate = function($el, $render, item, list){
			$render.show()
			$el.replaceWith($render);
		};

		app.subscribe(/^model:Host:create/, function(data, topic){
			let [a,b, action, host] = topic.split(':');
			if($.scope.hosts.indexOf(host) >= 0){
				$.scope.hosts.update(host, hostParseRow(data));
			}else{
				$.scope.hosts.unshift(hostParseRow(data));
			}
		});

		app.subscribe(/^model:Host:update/, function(data, topic){
			let [a,b, action, host] = topic.split(':');
			if($.scope.hosts.indexOf(host) >= 0){
				$.scope.hosts.update(host, hostParseRow(data));
			}else{
				$.scope.hosts.unshift(hostParseRow(data));
			}
		});

		app.subscribe(/^model:Host:remove/, function(data, topic){
			let [a,b, action, host] = topic.split(':');
			$.scope.hosts.remove(host);
		});
	});
</script>

<div class="container mt-4">
<div class="row" style="display:none">
	<div class="col-12">
		<div class="card shadow-lg hostListPanel">
			<div class="card-header d-flex align-items-center">
				<span class="card-icon me-2"><i class="fa-solid fa-network-wired"></i></span>
				<span class="card-title fw-bold">Proxy List</span>
				<span class="ms-auto">
					<a href="/docs/hosts" class="text-reset me-2" title="Help"><i class="fa-solid fa-circle-question"></i></a>
					<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="hostClearCache(this)" title="Clear cached wildcard subdomain lookups">
						<i class="fa-solid fa-broom"></i>
						Clear cache
					</button>
					<button type="button" class="btn btn-sm btn-success" onclick="hostAddOpen()">
						<i class="fa-solid fa-plus"></i>
						Add host
					</button>
				</span>
			</div>

			<div class="card-header actionMessage" style="display:none"></div>
			<div class="card-body search-wrapper">
				<label class="form-label" for="search" style="margin-left: -5px;">
					Search Hosts:
				</label>
				<input type="search" oninput="hostSearchInput()" />
			</div>
			<div class='table-responsive'>
				<table class="m-0 card-body table table-striped overflow-x-scroll">
					<thead>
						<th>
							<input type="checkbox"
								onclick="$('.host_checkbox:visible').each((i, element)=>{
									$(element).prop('checked', $(this).prop('checked'));
								})"
							/>
							<br />
							<button type="button" class="btn btn-sm btn-danger" onclick="$('.host_checkbox:checked').each((i, element)=>{
								app.api.delete(`host/${$(element).parents('[jq-repeat-index]').attr('jq-repeat-index')}`, function(){});
							})">
								<i class="fa-solid fa-trash-can"></i>
							</button>
						</th>
						<th>SSL Expire</th>
						<th>Host Name</th>
						<th>target</th>
						<th class="hidden-xs">Created</th>
						<th class="hidden-xs">Updated</th>
						<th>Actions</th>
					</thead>

					<tbody>
						<tr action="api" jq-repeat="hosts" jq-index-key='host' style="display:none">
							<td>
								<input type="checkbox" class="host_checkbox">
							</td>
							<td class="table-{{wildcard_text_bg}}">
								{{{ wildcard_text }}}
								{{#wildcard_expires}}
									<span class="momentFromNow" data-date="{{ wildcard_expires }}" >{{wildcard_expires_text}}</span>
								{{/wildcard_expires}}
							</td>
							<td>
								<a target="_blank" href="{{ forcessl_text }}{{ host }}">
									{{{ forcessl_text }}}{{ host }}
								</a>
								{{#domain.provider}}
									<br />
									<img width="24px" src="{{displayIconHtml}}" /> {{displayName}} - {{name}}
								{{/domain.provider}}

								{{#wildcard_parent}}
									<i>{{wildcard_parent}}</i>
								{{/wildcard_parent}}
							</td>
							<td>
								{{{ targetssl_text }}}{{ ip }}:{{ targetPort }}
							</td>
							<td class="hidden-xs momentFromNow" data-date="{{ created_on }}" title="Created by {{ created_by }}">
								{{ created_on_text }}
								<br />
								<small class="text-muted">{{ created_by }}</small>
							</td>
							<td class="hidden-xs momentFromNow" data-date="{{ updated_on }}" >
								{{ updated_on_text }}
							</td>
							<td>
								<div class="btn-group">
									<div class="btn-group" role="group">
										<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
											<i class="fa-brands fa-expeditedssl"></i>
											Certs
										</button>
										<ul class="dropdown-menu">
											<li>
												<button type="button" class="dropdown-item" onclick="hostDownloadCert('{{host}}', 'cert_pem')">
													<i class="fa-solid fa-certificate"></i>
													Cert
													<i class="fa-solid fa-file-arrow-down float-end"></i>
												</button>
											</li>
											<li>
												<button type="button" class="dropdown-item" onclick="hostDownloadCert('{{host}}', 'fullchain_pem')">
													<i class="fa-solid fa-link"></i>
													Full Chain
													<i class="fa-solid fa-file-arrow-down float-end"></i>
												</button>
											</li>
											<li>
												<button type="button" class="dropdown-item" onclick="hostDownloadCert('{{host}}', 'privkey_pem')">
													<i class="fa-solid fa-key"></i>
													Private key
													<i class="fa-solid fa-file-arrow-down float-end"></i>
												</button>
											</li>
										</ul>
									</div>

									<button type="button" onclick="hostEditOpen('{{ host }}');" class="btn btn-sm btn-warning">
										<i class="fa-solid fa-pencil"></i>
										Edit
									</button>
									<button type="button" method="DELETE" action="host/{{host}}" onclick="formAJAX()" class="btn btn-sm btn-danger">
										<i class="fa-solid fa-trash-can"></i>
										Delete
									</button>
								</div>
							</td>
						</tr>
					</tbody>
				</table>
			</div>
		</div>
	</div>
</div>
</div>

<%- include('bottom') %>
