mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
20221221 sync deploy (#1285)
This commit is contained in:
parent
c5bcc7aaf3
commit
def8f3f1bd
|
@ -23,7 +23,7 @@ ipa_ca = "/path/to/kanidm-ipa-ca.pem"
|
|||
# The DN of an account with content sync rights. By default cn=Directory Manager has
|
||||
# this access.
|
||||
ipa_sync_dn = "cn=Directory Manager"
|
||||
ipa_sync_pw = "pi9aix6balaqu8Maerah"
|
||||
ipa_sync_pw = "directory manager password"
|
||||
# The basedn to examine.
|
||||
ipa_sync_base_dn = "dc=ipa,dc=dev,dc=kanidm,dc=com"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
dn: cn=Retro Changelog Plugin,cn=plugins,cn=config
|
||||
changetype: modify
|
||||
add: nsslapd-include-suffix
|
||||
replace: nsslapd-include-suffix
|
||||
nsslapd-include-suffix: dc=ipa,dc=dev,dc=kanidm,dc=com
|
||||
|
|
|
@ -27,18 +27,44 @@ This example is located in [examples/kanidm-ipa-sync](https://github.com/kanidm/
|
|||
|
||||
In addition to this, you must make some configuration changes to FreeIPA to enable synchronisation.
|
||||
|
||||
You must modify the retro changelog plugin to include the full scope of the database suffix.
|
||||
You can find the name of your 389 Directory Server instance with:
|
||||
|
||||
dsconf --list
|
||||
|
||||
Using this you can show the current status of the retro changelog plugin to see if you need
|
||||
to change it's configuration.
|
||||
|
||||
dsconf <instance name> plugin retro-changelog show
|
||||
dsconf slapd-DEV-KANIDM-COM plugin retro-changelog show
|
||||
|
||||
You must modify the retro changelog plugin to include the full scope of the database suffix so that
|
||||
the sync tool can view the changes to the database. Currently dsconf can not modify the include-suffix
|
||||
so you must do this manually.
|
||||
|
||||
You need to change the `nsslapd-include-suffix` to match your FreeIPA baseDN here. You can
|
||||
access the basedn with:
|
||||
|
||||
ldapsearch -H ldaps://<IPA SERVER HOSTNAME/IP> -x -b '' -s base namingContexts
|
||||
# namingContexts: dc=ipa,dc=dev,dc=kanidm,dc=com
|
||||
|
||||
You should ignore `cn=changelog` and `o=ipaca` as these are system internal namingContexts. You
|
||||
can then create an ldapmodify like the following.
|
||||
|
||||
```
|
||||
{{#rustdoc_include ../../../iam_migrations/freeipa/00config-mod.ldif}}
|
||||
```
|
||||
|
||||
You must then restart your FreeIPA server.
|
||||
And apply it with:
|
||||
|
||||
ldapmodify -f change.ldif -H ldaps://<IPA SERVER HOSTNAME/IP> -x -D 'cn=Directory Manager' -W
|
||||
# Enter LDAP Password:
|
||||
|
||||
You must then reboot your FreeIPA server.
|
||||
|
||||
## Running the Sync Tool Manually
|
||||
|
||||
You can perform a dry run with the sync tool manually to check your configurations are
|
||||
correct.
|
||||
correct and that the tool can synchronise from FreeIPA.
|
||||
|
||||
kanidm-ipa-sync [-c /path/to/kanidm/config] -i /path/to/kanidm-ipa-sync -n
|
||||
kanidm-ipa-sync -i /etc/kanidm/ipa-sync -n
|
||||
|
|
|
@ -327,12 +327,14 @@ impl fmt::Display for AuthType {
|
|||
pub enum UiHint {
|
||||
ExperimentalFeatures = 0,
|
||||
PosixAccount = 1,
|
||||
CredentialUpdate = 2,
|
||||
}
|
||||
|
||||
impl fmt::Display for UiHint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
UiHint::PosixAccount => write!(f, "PosixAccount"),
|
||||
UiHint::CredentialUpdate => write!(f, "CredentialUpdate"),
|
||||
UiHint::ExperimentalFeatures => write!(f, "ExperimentalFeatures"),
|
||||
}
|
||||
}
|
||||
|
@ -343,6 +345,7 @@ impl FromStr for UiHint {
|
|||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"CredentialUpdate" => Ok(UiHint::CredentialUpdate),
|
||||
"PosixAccount" => Ok(UiHint::PosixAccount),
|
||||
"ExperimentalFeatures" => Ok(UiHint::ExperimentalFeatures),
|
||||
_ => Err(()),
|
||||
|
|
|
@ -99,6 +99,10 @@ macro_rules! try_from_entry {
|
|||
.copied()
|
||||
.collect();
|
||||
|
||||
if !$value.attribute_equality("class", &PVCLASS_SYNC_OBJECT) {
|
||||
ui_hints.insert(UiHint::CredentialUpdate);
|
||||
}
|
||||
|
||||
if $value.attribute_equality("class", &PVCLASS_POSIXACCOUNT) {
|
||||
ui_hints.insert(UiHint::PosixAccount);
|
||||
}
|
||||
|
@ -722,7 +726,8 @@ mod tests {
|
|||
.expect("Unable to create uat");
|
||||
|
||||
// Check the ui hints are as expected.
|
||||
assert!(uat.ui_hints.is_empty());
|
||||
assert!(uat.ui_hints.len() == 1);
|
||||
assert!(uat.ui_hints.contains(&UiHint::CredentialUpdate));
|
||||
|
||||
// Modify the user to be a posix account, ensure they get the hint.
|
||||
let me_posix = unsafe {
|
||||
|
@ -748,8 +753,9 @@ mod tests {
|
|||
.to_userauthtoken(session_id, ct, AuthType::Passkey, None)
|
||||
.expect("Unable to create uat");
|
||||
|
||||
assert!(uat.ui_hints.len() == 1);
|
||||
assert!(uat.ui_hints.len() == 2);
|
||||
assert!(uat.ui_hints.contains(&UiHint::PosixAccount));
|
||||
assert!(uat.ui_hints.contains(&UiHint::CredentialUpdate));
|
||||
|
||||
// Add a group with a ui hint, and then check they get the hint.
|
||||
let e = entry_init!(
|
||||
|
@ -772,9 +778,10 @@ mod tests {
|
|||
.to_userauthtoken(session_id, ct, AuthType::Passkey, None)
|
||||
.expect("Unable to create uat");
|
||||
|
||||
assert!(uat.ui_hints.len() == 2);
|
||||
assert!(uat.ui_hints.len() == 3);
|
||||
assert!(uat.ui_hints.contains(&UiHint::PosixAccount));
|
||||
assert!(uat.ui_hints.contains(&UiHint::ExperimentalFeatures));
|
||||
assert!(uat.ui_hints.contains(&UiHint::CredentialUpdate));
|
||||
|
||||
assert!(idms_prox_write.commit().is_ok());
|
||||
})
|
||||
|
|
|
@ -233,7 +233,7 @@ function addBorrowedObject(obj) {
|
|||
}
|
||||
function __wbg_adapter_48(arg0, arg1, arg2) {
|
||||
try {
|
||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h871c42aaca1d3703(arg0, arg1, addBorrowedObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hdb7f5bd2101eb559(arg0, arg1, addBorrowedObject(arg2));
|
||||
} finally {
|
||||
heap[stack_pointer++] = undefined;
|
||||
}
|
||||
|
@ -261,11 +261,11 @@ function makeClosure(arg0, arg1, dtor, f) {
|
|||
return real;
|
||||
}
|
||||
function __wbg_adapter_51(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6073fca8e152ad8e(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd6c53cf713a6c0ff(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_54(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h501c623b6f23b8ff(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h3b581d83a45c2264(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -345,6 +345,10 @@ async function load(module, imports) {
|
|||
function getImports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
||||
const ret = getObject(arg0) === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof(obj) === 'number' ? obj : undefined;
|
||||
|
@ -386,10 +390,6 @@ function getImports() {
|
|||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
||||
const ret = getObject(arg0) === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_in = function(arg0, arg1) {
|
||||
const ret = getObject(arg0) in getObject(arg1);
|
||||
return ret;
|
||||
|
@ -542,6 +542,29 @@ function getImports() {
|
|||
imports.wbg.__wbg_log_4b5638ad60bdc54a = function(arg0) {
|
||||
console.log(getObject(arg0));
|
||||
};
|
||||
imports.wbg.__wbg_getItem_845e475f85f593e4 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||
const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));
|
||||
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_removeItem_9da69ede4eea3326 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
getObject(arg0).removeItem(getStringFromWasm0(arg1, arg2));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_setItem_9c469d634d0c321c = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_instanceof_HtmlFormElement_1c489ff7e99e43d3 = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof HTMLFormElement;
|
||||
} catch {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_value_ccb32485ee1b3928 = function(arg0, arg1) {
|
||||
const ret = getObject(arg1).value;
|
||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
|
@ -566,39 +589,6 @@ function getImports() {
|
|||
imports.wbg.__wbg_set_992c1d31586b2957 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
getObject(arg0).set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_instanceof_HtmlFormElement_1c489ff7e99e43d3 = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof HTMLFormElement;
|
||||
} catch {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_getItem_845e475f85f593e4 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||
const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));
|
||||
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_removeItem_9da69ede4eea3326 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
getObject(arg0).removeItem(getStringFromWasm0(arg1, arg2));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_setItem_9c469d634d0c321c = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_new_ca4d3a3eca340210 = function() { return handleError(function () {
|
||||
const ret = new URLSearchParams();
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_add_89a4f3b0846cf0aa = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
getObject(arg0).add(getStringFromWasm0(arg1, arg2));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_remove_1a26eb5d822902ed = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
getObject(arg0).remove(getStringFromWasm0(arg1, arg2));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_pathname_78a642e573bf8169 = function(arg0, arg1) {
|
||||
const ret = getObject(arg1).pathname;
|
||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
|
@ -620,6 +610,10 @@ function getImports() {
|
|||
const ret = new URL(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_new_ca4d3a3eca340210 = function() { return handleError(function () {
|
||||
const ret = new URLSearchParams();
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_instanceof_HtmlInputElement_970e4026de0fccff = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
|
@ -643,13 +637,11 @@ function getImports() {
|
|||
imports.wbg.__wbg_setvalue_e5b519cca37d82a7 = function(arg0, arg1, arg2) {
|
||||
getObject(arg0).value = getStringFromWasm0(arg1, arg2);
|
||||
};
|
||||
imports.wbg.__wbg_create_53c6ddb068a22172 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = getObject(arg0).create(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
imports.wbg.__wbg_add_89a4f3b0846cf0aa = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
getObject(arg0).add(getStringFromWasm0(arg1, arg2));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_get_da97585bbb5a63bb = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = getObject(arg0).get(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
imports.wbg.__wbg_remove_1a26eb5d822902ed = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
getObject(arg0).remove(getStringFromWasm0(arg1, arg2));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_href_90ff36b5040e3b76 = function(arg0, arg1) {
|
||||
const ret = getObject(arg1).href;
|
||||
|
@ -720,10 +712,14 @@ function getImports() {
|
|||
imports.wbg.__wbg_focus_adfe4cc61e2c09bc = function() { return handleError(function (arg0) {
|
||||
getObject(arg0).focus();
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_credentials_eab5c0bffc3e9cc5 = function(arg0) {
|
||||
const ret = getObject(arg0).credentials;
|
||||
imports.wbg.__wbg_create_53c6ddb068a22172 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = getObject(arg0).create(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_get_da97585bbb5a63bb = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = getObject(arg0).get(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_getClientExtensionResults_0381c2792f96b9fa = function(arg0) {
|
||||
const ret = getObject(arg0).getClientExtensionResults();
|
||||
return addHeapObject(ret);
|
||||
|
@ -757,6 +753,10 @@ function getImports() {
|
|||
const ret = getObject(arg0).get(getStringFromWasm0(arg1, arg2));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_credentials_eab5c0bffc3e9cc5 = function(arg0) {
|
||||
const ret = getObject(arg0).credentials;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_addEventListener_1fc744729ac6dc27 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), getObject(arg4));
|
||||
}, arguments) };
|
||||
|
@ -786,6 +786,20 @@ function getImports() {
|
|||
const ret = getObject(arg0).removeChild(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_instanceof_WorkerGlobalScope_16bb97a4549a3f21 = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof WorkerGlobalScope;
|
||||
} catch {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_fetch_749a56934f95c96c = function(arg0, arg1) {
|
||||
const ret = getObject(arg0).fetch(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_pushState_38917fb88b4add30 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) {
|
||||
getObject(arg0).pushState(getObject(arg1), getStringFromWasm0(arg2, arg3), arg4 === 0 ? undefined : getStringFromWasm0(arg4, arg5));
|
||||
}, arguments) };
|
||||
|
@ -1016,10 +1030,6 @@ function getImports() {
|
|||
const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) {
|
||||
const ret = JSON.stringify(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_bigint_get_as_i64 = function(arg0, arg1) {
|
||||
const v = getObject(arg1);
|
||||
const ret = typeof(v) === 'bigint' ? v : undefined;
|
||||
|
@ -1040,16 +1050,16 @@ function getImports() {
|
|||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper4814 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1014, __wbg_adapter_48);
|
||||
imports.wbg.__wbindgen_closure_wrapper4824 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1020, __wbg_adapter_48);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper5000 = function(arg0, arg1, arg2) {
|
||||
const ret = makeClosure(arg0, arg1, 1041, __wbg_adapter_51);
|
||||
imports.wbg.__wbindgen_closure_wrapper5009 = function(arg0, arg1, arg2) {
|
||||
const ret = makeClosure(arg0, arg1, 1044, __wbg_adapter_51);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper5658 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1290, __wbg_adapter_54);
|
||||
imports.wbg.__wbindgen_closure_wrapper5669 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1294, __wbg_adapter_54);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
|
|
Binary file not shown.
|
@ -237,6 +237,7 @@ impl ViewsApp {
|
|||
let current_user_uat = uat.clone();
|
||||
|
||||
let ui_hint_experimental = uat.ui_hints.contains(&UiHint::ExperimentalFeatures);
|
||||
let credential_update = uat.ui_hints.contains(&UiHint::CredentialUpdate);
|
||||
|
||||
// WARN set dash-body against body here?
|
||||
html! {
|
||||
|
@ -269,12 +270,14 @@ impl ViewsApp {
|
|||
</li>
|
||||
}
|
||||
|
||||
<li class="mb-1">
|
||||
<Link<ViewRoute> classes="nav-link" to={ViewRoute::Security}>
|
||||
<span data-feather="file"></span>
|
||||
{ "Security" }
|
||||
</Link<ViewRoute>>
|
||||
</li>
|
||||
if credential_update {
|
||||
<li class="mb-1">
|
||||
<Link<ViewRoute> classes="nav-link" to={ViewRoute::Security}>
|
||||
<span data-feather="file"></span>
|
||||
{ "Security" }
|
||||
</Link<ViewRoute>>
|
||||
</li>
|
||||
}
|
||||
|
||||
if ui_hint_experimental {
|
||||
<li class="mb-1">
|
||||
|
@ -292,10 +295,10 @@ impl ViewsApp {
|
|||
>{"Sign out"}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="d-flex">
|
||||
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" />
|
||||
<button class="btn btn-outline-light" type="submit">{"Search"}</button>
|
||||
</form>
|
||||
// <form class="d-flex">
|
||||
// <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" />
|
||||
// <button class="btn btn-outline-light" type="submit">{"Search"}</button>
|
||||
// </form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
Loading…
Reference in a new issue