Compare commits
4 Commits
| Author | SHA256 | Date | |
|---|---|---|---|
| b0ea2e0ee3 | |||
| 4fb49797cb | |||
| bb3f5ed1f0 | |||
| 078589332c |
Generated
+14
-695
File diff suppressed because it is too large
Load Diff
+12
-4
@@ -3,9 +3,12 @@ name = "kira-installer"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
# Reference the build script
|
||||||
|
# build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
blocking = "1.6"
|
blocking = "1.6"
|
||||||
iced = { version = "0.14", default-features = false, features = ["crisp", "linux-theme-detection", "wgpu", "x11", "wayland", "smol", "image"] }
|
iced = { version = "0.14", default-features = false, features = ["crisp", "linux-theme-detection", "tiny-skia", "x11", "smol", "image"] }
|
||||||
iced_moving_picture = "0"
|
iced_moving_picture = "0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
rust-i18n = "3"
|
rust-i18n = "3"
|
||||||
@@ -15,10 +18,15 @@ toml = "1"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.10"
|
rand = "0.10"
|
||||||
|
|
||||||
[profile.release]
|
[profile.dev]
|
||||||
#lto = true
|
opt-level = 2
|
||||||
debug = false
|
|
||||||
codegen-units = 16
|
codegen-units = 16
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
debug = false
|
||||||
|
incremental = false
|
||||||
|
codegen-units = 1
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let profile = env::var("PROFILE").unwrap();
|
||||||
|
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||||
|
let target_dir = PathBuf::from(&manifest_dir).join("target").join(&profile);
|
||||||
|
|
||||||
|
// Ensure target directory exists
|
||||||
|
if !target_dir.exists() {
|
||||||
|
fs::create_dir_all(&target_dir).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the file (e.g., config.json) to the target directory
|
||||||
|
let source = PathBuf::from(&manifest_dir).join("src").join("kira_config.toml");
|
||||||
|
let dest = target_dir.join("kira_config.toml");
|
||||||
|
|
||||||
|
fs::copy(&source, &dest).expect("Failed to copy kira_config.toml");
|
||||||
|
|
||||||
|
// Optional: Tell Cargo to re-run this script if the source file changes
|
||||||
|
println!("cargo:rerun-if-changed=src/kira_config.toml");
|
||||||
|
}
|
||||||
+12
-5
@@ -1,4 +1,4 @@
|
|||||||
use iced::widget::{Row, container, text};
|
use iced::widget::{self, Row, container, text};
|
||||||
use iced::{Alignment, Color, Element, Length};
|
use iced::{Alignment, Color, Element, Length};
|
||||||
|
|
||||||
// Assuming your Item struct or type implements Display or has a text representation
|
// Assuming your Item struct or type implements Display or has a text representation
|
||||||
@@ -6,10 +6,9 @@ pub fn color_bar<Message: 'static>(
|
|||||||
maybe_items: Option<Vec<(String, u16, Color)>>,
|
maybe_items: Option<Vec<(String, u16, Color)>>,
|
||||||
bar_height: u32,
|
bar_height: u32,
|
||||||
) -> Element<'static, Message> {
|
) -> Element<'static, Message> {
|
||||||
let mut c_b = Row::new().height(bar_height);
|
|
||||||
|
|
||||||
if let Some(items) = maybe_items {
|
if let Some(items) = maybe_items {
|
||||||
|
let mut c_b = Row::new().height(Length::Fill);
|
||||||
for (bar_text, bar_fill, bar_color) in items {
|
for (bar_text, bar_fill, bar_color) in items {
|
||||||
c_b = c_b.push(
|
c_b = c_b.push(
|
||||||
container(text(bar_text).color(Color::BLACK).size(iced::Pixels(14.0)))
|
container(text(bar_text).color(Color::BLACK).size(iced::Pixels(14.0)))
|
||||||
@@ -21,9 +20,17 @@ pub fn color_bar<Message: 'static>(
|
|||||||
.padding(2),
|
.padding(2),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
container(c_b.spacing(2))
|
||||||
container(c_b.spacing(2))
|
|
||||||
.style(container::rounded_box)
|
.style(container::rounded_box)
|
||||||
.padding(3)
|
.padding(3)
|
||||||
|
.height(bar_height)
|
||||||
.into()
|
.into()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
widget::container(widget::space())
|
||||||
|
.height(bar_height)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"partition.no_swap": "No swap partition",
|
"partition.no_swap": "No swap partition",
|
||||||
"partition.swap_no_hibernate": "Swap without hibernate",
|
"partition.swap_no_hibernate": "Swap without hibernate",
|
||||||
"partition.swap_hibernate": "Swap with hibernation",
|
"partition.swap_hibernate": "Swap with hibernation",
|
||||||
|
"partition.swap_custom": "Custom swap size",
|
||||||
"partition.select_device_placeholder": "Select Device",
|
"partition.select_device_placeholder": "Select Device",
|
||||||
"partition.select_device": "Select device for system installation",
|
"partition.select_device": "Select device for system installation",
|
||||||
"partition.select_swapmode_placeholder": "Select Swap Mode",
|
"partition.select_swapmode_placeholder": "Select Swap Mode",
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ impl NetworkStage {
|
|||||||
logo_handle: logo_handle,
|
logo_handle: logo_handle,
|
||||||
internet_active: false,
|
internet_active: false,
|
||||||
state: State::Cheking,
|
state: State::Cheking,
|
||||||
use_net_settings: false,
|
use_net_settings: true,
|
||||||
reqire_network: reqire_network,
|
reqire_network: reqire_network,
|
||||||
spinner_frames: spinner_frames,
|
spinner_frames: spinner_frames,
|
||||||
status_message: t!("network.checking").into(),
|
status_message: t!("network.checking").into(),
|
||||||
|
|||||||
+98
-40
@@ -18,22 +18,24 @@
|
|||||||
This is partition stage!
|
This is partition stage!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use log;
|
use std::char;
|
||||||
use crate::stage::{StageAction, StageResult};
|
|
||||||
use crate::kira_color_bar;
|
use crate::kira_color_bar;
|
||||||
use crate::kira_disk_layout::{BlkDev, FSType, PartInfo, PartLayout};
|
use crate::kira_disk_layout::{BlkDev, FSType, PartInfo, PartLayout};
|
||||||
use crate::kira_size::KiraSize;
|
use crate::kira_size::KiraSize;
|
||||||
use crate::kira_sysinfo;
|
use crate::kira_sysinfo;
|
||||||
use iced::{Alignment, Color, widget};
|
use crate::stage::{StageAction, StageResult};
|
||||||
|
use iced::{Alignment, Color, Element, Length, widget};
|
||||||
|
use log;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum SwapMode {
|
pub enum SwapMode {
|
||||||
NoSwap,
|
NoSwap,
|
||||||
SwapNoHibernate,
|
SwapNoHibernate,
|
||||||
SwapHibernate,
|
SwapHibernate,
|
||||||
|
SwapCustom,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for SwapMode {
|
impl std::fmt::Display for SwapMode {
|
||||||
@@ -42,14 +44,16 @@ impl std::fmt::Display for SwapMode {
|
|||||||
Self::NoSwap => f.write_str(t!("partition.no_swap").as_ref()),
|
Self::NoSwap => f.write_str(t!("partition.no_swap").as_ref()),
|
||||||
Self::SwapNoHibernate => f.write_str(t!("partition.swap_no_hibernate").as_ref()),
|
Self::SwapNoHibernate => f.write_str(t!("partition.swap_no_hibernate").as_ref()),
|
||||||
Self::SwapHibernate => f.write_str(t!("partition.swap_hibernate").as_ref()),
|
Self::SwapHibernate => f.write_str(t!("partition.swap_hibernate").as_ref()),
|
||||||
|
Self::SwapCustom => f.write_str(t!("partition.swap_custom").as_ref()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SWAP_MODES: [SwapMode; 3] = [
|
const SWAP_MODES: [SwapMode; 4] = [
|
||||||
SwapMode::NoSwap,
|
SwapMode::NoSwap,
|
||||||
SwapMode::SwapNoHibernate,
|
SwapMode::SwapNoHibernate,
|
||||||
SwapMode::SwapHibernate,
|
SwapMode::SwapHibernate,
|
||||||
|
SwapMode::SwapCustom,
|
||||||
];
|
];
|
||||||
|
|
||||||
fn part_type_to_color(t: &FSType) -> Color {
|
fn part_type_to_color(t: &FSType) -> Color {
|
||||||
@@ -78,6 +82,8 @@ pub struct PartitionStage {
|
|||||||
swap_mode: Option<SwapMode>,
|
swap_mode: Option<SwapMode>,
|
||||||
use_zram: bool,
|
use_zram: bool,
|
||||||
secure_boot: bool,
|
secure_boot: bool,
|
||||||
|
custom_swap_size_mb: u32,
|
||||||
|
custom_swap_size_str: String,
|
||||||
selected_disk_parts: Option<Vec<(String, u16, Color)>>,
|
selected_disk_parts: Option<Vec<(String, u16, Color)>>,
|
||||||
generated_disk_parts: Option<Vec<(String, u16, Color)>>,
|
generated_disk_parts: Option<Vec<(String, u16, Color)>>,
|
||||||
system_ram_size: KiraSize,
|
system_ram_size: KiraSize,
|
||||||
@@ -90,6 +96,9 @@ pub enum Message {
|
|||||||
SelectSwapMode(SwapMode),
|
SelectSwapMode(SwapMode),
|
||||||
ToggleZram(bool),
|
ToggleZram(bool),
|
||||||
ToggleSecureBoot(bool),
|
ToggleSecureBoot(bool),
|
||||||
|
SwapSizeFieldChange(String),
|
||||||
|
SwapSizeIncrement,
|
||||||
|
SwapSizeDecrement,
|
||||||
Next,
|
Next,
|
||||||
Back,
|
Back,
|
||||||
}
|
}
|
||||||
@@ -125,7 +134,8 @@ impl PartitionStage {
|
|||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"stage init ram {} vram {}",
|
"stage init ram {} vram {}",
|
||||||
system_ram_size, vram_of_all_gpus_size
|
system_ram_size,
|
||||||
|
vram_of_all_gpus_size
|
||||||
);
|
);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
device: None,
|
device: None,
|
||||||
@@ -133,6 +143,8 @@ impl PartitionStage {
|
|||||||
swap_mode: Some(SwapMode::SwapNoHibernate),
|
swap_mode: Some(SwapMode::SwapNoHibernate),
|
||||||
use_zram: true,
|
use_zram: true,
|
||||||
secure_boot: false,
|
secure_boot: false,
|
||||||
|
custom_swap_size_mb: 1024,
|
||||||
|
custom_swap_size_str: "1024MB".into(),
|
||||||
selected_disk_parts: None,
|
selected_disk_parts: None,
|
||||||
generated_disk_parts: None,
|
generated_disk_parts: None,
|
||||||
system_ram_size: system_ram_size,
|
system_ram_size: system_ram_size,
|
||||||
@@ -144,11 +156,17 @@ impl PartitionStage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_swap_size(&self) -> KiraSize {
|
fn get_swap_size(&self) -> KiraSize {
|
||||||
|
if let Some(s_mode) = self.swap_mode
|
||||||
|
&& s_mode == SwapMode::SwapCustom
|
||||||
|
{
|
||||||
|
return KiraSize::new_mb(self.custom_swap_size_mb as u64);
|
||||||
|
}
|
||||||
|
|
||||||
let total_ram = self.system_ram_size + self.vram_of_all_gpus_size;
|
let total_ram = self.system_ram_size + self.vram_of_all_gpus_size;
|
||||||
log::debug!("total_ram {}", total_ram);
|
|
||||||
let sys_ram_gb = self.system_ram_size.gb();
|
let sys_ram_gb = self.system_ram_size.gb();
|
||||||
log::debug!("sys_ram_gb {}", sys_ram_gb);
|
log::debug!("sys_ram_gb {}", sys_ram_gb);
|
||||||
let disk_swap_gb = if sys_ram_gb >= 32 {
|
let mut disk_swap_gb = if sys_ram_gb >= 32 {
|
||||||
1u64
|
1u64
|
||||||
} else if sys_ram_gb >= 24 {
|
} else if sys_ram_gb >= 24 {
|
||||||
2u64
|
2u64
|
||||||
@@ -160,9 +178,11 @@ impl PartitionStage {
|
|||||||
sys_ram_gb * 2
|
sys_ram_gb * 2
|
||||||
};
|
};
|
||||||
|
|
||||||
log::debug!("disk_swap_gb {}", disk_swap_gb);
|
if self.use_zram {
|
||||||
|
disk_swap_gb = disk_swap_gb.saturating_sub(sys_ram_gb).max(2);
|
||||||
|
}
|
||||||
let disk_swap = KiraSize::new_gb(disk_swap_gb);
|
let disk_swap = KiraSize::new_gb(disk_swap_gb);
|
||||||
log::debug!("disk_swap {}", disk_swap);
|
|
||||||
match self.swap_mode {
|
match self.swap_mode {
|
||||||
Some(SwapMode::NoSwap) => KiraSize::new_b(0),
|
Some(SwapMode::NoSwap) => KiraSize::new_b(0),
|
||||||
Some(SwapMode::SwapHibernate) => disk_swap + total_ram,
|
Some(SwapMode::SwapHibernate) => disk_swap + total_ram,
|
||||||
@@ -171,6 +191,19 @@ impl PartitionStage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gen_layout(&mut self) {
|
||||||
|
if let Some(dev) = self.device.clone() {
|
||||||
|
let swap_size: KiraSize = self.get_swap_size();
|
||||||
|
self.generated_disk_parts = Some(
|
||||||
|
PartLayout::gen_classic_layout(dev.clone(), KiraSize::new_mb(512), swap_size)
|
||||||
|
.part_list
|
||||||
|
.iter()
|
||||||
|
.map(|part_info| part_to_ct_params(part_info, dev.size))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn gen_result(&self) -> StageResult {
|
fn gen_result(&self) -> StageResult {
|
||||||
StageResult::new("partition")
|
StageResult::new("partition")
|
||||||
.add_val_string(
|
.add_val_string(
|
||||||
@@ -224,22 +257,7 @@ impl PartitionStage {
|
|||||||
|
|
||||||
Message::SelectSwapMode(sm) => {
|
Message::SelectSwapMode(sm) => {
|
||||||
self.swap_mode = Some(sm);
|
self.swap_mode = Some(sm);
|
||||||
|
self.gen_layout();
|
||||||
if let Some(dev) = self.device.clone() {
|
|
||||||
let swap_size: KiraSize = self.get_swap_size();
|
|
||||||
self.generated_disk_parts = Some(
|
|
||||||
PartLayout::gen_classic_layout(
|
|
||||||
dev.clone(),
|
|
||||||
KiraSize::new_mb(512),
|
|
||||||
swap_size,
|
|
||||||
)
|
|
||||||
.part_list
|
|
||||||
.iter()
|
|
||||||
.map(|part_info| part_to_ct_params(part_info, dev.size))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
StageAction::None
|
StageAction::None
|
||||||
}
|
}
|
||||||
Message::ToggleSecureBoot(t) => {
|
Message::ToggleSecureBoot(t) => {
|
||||||
@@ -248,6 +266,31 @@ impl PartitionStage {
|
|||||||
}
|
}
|
||||||
Message::ToggleZram(t) => {
|
Message::ToggleZram(t) => {
|
||||||
self.use_zram = t;
|
self.use_zram = t;
|
||||||
|
self.gen_layout();
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::SwapSizeIncrement => {
|
||||||
|
self.custom_swap_size_mb = (self.custom_swap_size_mb / 1024u32)
|
||||||
|
.strict_mul(1024u32)
|
||||||
|
.saturating_add(1024u32);
|
||||||
|
self.custom_swap_size_str = format!("{}MB", self.custom_swap_size_mb);
|
||||||
|
self.gen_layout();
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::SwapSizeDecrement => {
|
||||||
|
self.custom_swap_size_mb = (self.custom_swap_size_mb / 1024u32)
|
||||||
|
.strict_mul(1024u32)
|
||||||
|
.saturating_sub(1024u32);
|
||||||
|
self.custom_swap_size_str = format!("{}MB", self.custom_swap_size_mb);
|
||||||
|
self.gen_layout();
|
||||||
|
StageAction::None
|
||||||
|
}
|
||||||
|
Message::SwapSizeFieldChange(f_v) => {
|
||||||
|
let mut only_num = f_v.trim().to_string();
|
||||||
|
only_num.retain(char::is_numeric);
|
||||||
|
self.custom_swap_size_mb = u32::from_str_radix(&only_num, 10).unwrap_or(1024);
|
||||||
|
self.custom_swap_size_str = format!("{}MB", self.custom_swap_size_mb);
|
||||||
|
self.gen_layout();
|
||||||
StageAction::None
|
StageAction::None
|
||||||
}
|
}
|
||||||
Message::Back => StageAction::Back,
|
Message::Back => StageAction::Back,
|
||||||
@@ -256,11 +299,24 @@ impl PartitionStage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&self) -> iced::Element<'_, Message> {
|
pub fn view(&self) -> iced::Element<'_, Message> {
|
||||||
|
let swap_size_select: Element<'_, _> = match self.swap_mode {
|
||||||
|
Some(SwapMode::SwapCustom) => widget::row![
|
||||||
|
widget::text_input("", &self.custom_swap_size_str)
|
||||||
|
.width(100)
|
||||||
|
.on_input(Message::SwapSizeFieldChange),
|
||||||
|
widget::button("+").on_press(Message::SwapSizeIncrement),
|
||||||
|
widget::button("-").on_press(Message::SwapSizeDecrement),
|
||||||
|
]
|
||||||
|
.spacing(2)
|
||||||
|
.into(),
|
||||||
|
_ => widget::space().into(),
|
||||||
|
};
|
||||||
|
|
||||||
let dev_parts_text = match self.generated_disk_parts {
|
let dev_parts_text = match self.generated_disk_parts {
|
||||||
Some(_) => widget::text(t!("partition.current_after_install")),
|
Some(_) => widget::text(t!("partition.current_after_install")),
|
||||||
None => widget::text(""),
|
None => widget::text(""),
|
||||||
};
|
};
|
||||||
let dev_parts_content = kira_color_bar::color_bar(self.generated_disk_parts.clone(), 56);
|
let dev_parts_content = kira_color_bar::color_bar(self.generated_disk_parts.clone(), 64);
|
||||||
let dev_parts_content = widget::column![dev_parts_text, dev_parts_content];
|
let dev_parts_content = widget::column![dev_parts_text, dev_parts_content];
|
||||||
|
|
||||||
let back_button = widget::button(widget::text(t!("button.back"))).on_press(Message::Back);
|
let back_button = widget::button(widget::text(t!("button.back"))).on_press(Message::Back);
|
||||||
@@ -270,36 +326,38 @@ impl PartitionStage {
|
|||||||
Some(_) => widget::text(t!("partition.current_dev_layout")),
|
Some(_) => widget::text(t!("partition.current_dev_layout")),
|
||||||
None => widget::text(""),
|
None => widget::text(""),
|
||||||
};
|
};
|
||||||
let selected_dev_content = kira_color_bar::color_bar(self.selected_disk_parts.clone(), 56);
|
let selected_dev_content = kira_color_bar::color_bar(self.selected_disk_parts.clone(), 64);
|
||||||
let selected_dev_content = widget::column![selected_dev_text, selected_dev_content];
|
let selected_dev_content = widget::column![selected_dev_text, selected_dev_content];
|
||||||
|
|
||||||
widget::column![
|
widget::column![
|
||||||
widget::container(
|
widget::container(
|
||||||
widget::column![
|
widget::column![
|
||||||
widget::row![
|
widget::rule::horizontal(2),
|
||||||
widget::pick_list(
|
widget::text(t!("partition.select_device")),
|
||||||
self.devices.clone(),
|
widget::pick_list(
|
||||||
self.device.clone(),
|
self.devices.clone(),
|
||||||
Message::SelectDevice,
|
self.device.clone(),
|
||||||
)
|
Message::SelectDevice,
|
||||||
.placeholder(t!("partition.select_device_placeholder")),
|
)
|
||||||
widget::text(t!("partition.select_device"))
|
.placeholder(t!("partition.select_device_placeholder")),
|
||||||
]
|
widget::rule::horizontal(2),
|
||||||
.spacing(5)
|
widget::text(t!("partition.select_swapmode")),
|
||||||
.align_y(Alignment::Center),
|
|
||||||
widget::row![
|
widget::row![
|
||||||
widget::pick_list(SWAP_MODES, self.swap_mode, Message::SelectSwapMode,)
|
widget::pick_list(SWAP_MODES, self.swap_mode, Message::SelectSwapMode,)
|
||||||
.placeholder(t!("partition.select_swapmode_placeholder")),
|
.placeholder(t!("partition.select_swapmode_placeholder")),
|
||||||
widget::text(t!("partition.select_swapmode"))
|
swap_size_select
|
||||||
]
|
]
|
||||||
.spacing(5)
|
.spacing(5)
|
||||||
|
.width(Length::Shrink)
|
||||||
.align_y(Alignment::Center),
|
.align_y(Alignment::Center),
|
||||||
widget::checkbox(self.use_zram)
|
widget::checkbox(self.use_zram)
|
||||||
.label(t!("partition.use_zram"))
|
.label(t!("partition.use_zram"))
|
||||||
.on_toggle(Message::ToggleZram),
|
.on_toggle(Message::ToggleZram),
|
||||||
|
widget::rule::horizontal(2),
|
||||||
widget::checkbox(self.secure_boot)
|
widget::checkbox(self.secure_boot)
|
||||||
.label(t!("partition.use_secure_boot"))
|
.label(t!("partition.use_secure_boot"))
|
||||||
.on_toggle(Message::ToggleSecureBoot),
|
.on_toggle(Message::ToggleSecureBoot),
|
||||||
|
widget::rule::horizontal(2),
|
||||||
selected_dev_content,
|
selected_dev_content,
|
||||||
dev_parts_content,
|
dev_parts_content,
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user