mirror of
https://git.proxmox.com/git/proxmox
synced 2025-05-30 21:51:38 +00:00
move drive config to pbs_config workspace
Also moved the tape type definitions to pbs_api_types.
This commit is contained in:
parent
1c30b9da92
commit
28e668ddf3
@ -61,6 +61,9 @@ pub mod file_restore;
|
|||||||
mod remote;
|
mod remote;
|
||||||
pub use remote::*;
|
pub use remote::*;
|
||||||
|
|
||||||
|
mod tape;
|
||||||
|
pub use tape::*;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod local_macros {
|
mod local_macros {
|
||||||
|
135
pbs-api-types/src/tape/changer.rs
Normal file
135
pbs-api-types/src/tape/changer.rs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
//! Types for tape changer API
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use proxmox::api::{
|
||||||
|
api,
|
||||||
|
schema::{
|
||||||
|
Schema,
|
||||||
|
ApiStringFormat,
|
||||||
|
ArraySchema,
|
||||||
|
IntegerSchema,
|
||||||
|
StringSchema,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
PROXMOX_SAFE_ID_FORMAT,
|
||||||
|
OptionalDeviceIdentification,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CHANGER_NAME_SCHEMA: Schema = StringSchema::new("Tape Changer Identifier.")
|
||||||
|
.format(&PROXMOX_SAFE_ID_FORMAT)
|
||||||
|
.min_length(3)
|
||||||
|
.max_length(32)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const SCSI_CHANGER_PATH_SCHEMA: Schema = StringSchema::new(
|
||||||
|
"Path to Linux generic SCSI device (e.g. '/dev/sg4')")
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const MEDIA_LABEL_SCHEMA: Schema = StringSchema::new("Media Label/Barcode.")
|
||||||
|
.format(&PROXMOX_SAFE_ID_FORMAT)
|
||||||
|
.min_length(2)
|
||||||
|
.max_length(32)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const SLOT_ARRAY_SCHEMA: Schema = ArraySchema::new(
|
||||||
|
"Slot list.", &IntegerSchema::new("Slot number")
|
||||||
|
.minimum(1)
|
||||||
|
.schema())
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const EXPORT_SLOT_LIST_SCHEMA: Schema = StringSchema::new("\
|
||||||
|
A list of slot numbers, comma separated. Those slots are reserved for
|
||||||
|
Import/Export, i.e. any media in those slots are considered to be
|
||||||
|
'offline'.
|
||||||
|
")
|
||||||
|
.format(&ApiStringFormat::PropertyString(&SLOT_ARRAY_SCHEMA))
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
schema: CHANGER_NAME_SCHEMA,
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
schema: SCSI_CHANGER_PATH_SCHEMA,
|
||||||
|
},
|
||||||
|
"export-slots": {
|
||||||
|
schema: EXPORT_SLOT_LIST_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// SCSI tape changer
|
||||||
|
pub struct ScsiTapeChanger {
|
||||||
|
pub name: String,
|
||||||
|
pub path: String,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub export_slots: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
config: {
|
||||||
|
type: ScsiTapeChanger,
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
type: OptionalDeviceIdentification,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Changer config with optional device identification attributes
|
||||||
|
pub struct ChangerListEntry {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub config: ScsiTapeChanger,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub info: OptionalDeviceIdentification,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api()]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Mtx Entry Kind
|
||||||
|
pub enum MtxEntryKind {
|
||||||
|
/// Drive
|
||||||
|
Drive,
|
||||||
|
/// Slot
|
||||||
|
Slot,
|
||||||
|
/// Import/Export Slot
|
||||||
|
ImportExport,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
"entry-kind": {
|
||||||
|
type: MtxEntryKind,
|
||||||
|
},
|
||||||
|
"label-text": {
|
||||||
|
schema: MEDIA_LABEL_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Mtx Status Entry
|
||||||
|
pub struct MtxStatusEntry {
|
||||||
|
pub entry_kind: MtxEntryKind,
|
||||||
|
/// The ID of the slot or drive
|
||||||
|
pub entry_id: u64,
|
||||||
|
/// The media label (volume tag) if the slot/drive is full
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub label_text: Option<String>,
|
||||||
|
/// The slot the drive was loaded from
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub loaded_slot: Option<u64>,
|
||||||
|
/// The current state of the drive
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub state: Option<String>,
|
||||||
|
}
|
55
pbs-api-types/src/tape/device.rs
Normal file
55
pbs-api-types/src/tape/device.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use ::serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use proxmox::api::api;
|
||||||
|
|
||||||
|
#[api()]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Optional Device Identification Attributes
|
||||||
|
pub struct OptionalDeviceIdentification {
|
||||||
|
/// Vendor (autodetected)
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub vendor: Option<String>,
|
||||||
|
/// Model (autodetected)
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub model: Option<String>,
|
||||||
|
/// Serial number (autodetected)
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub serial: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api()]
|
||||||
|
#[derive(Debug,Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Kind of device
|
||||||
|
pub enum DeviceKind {
|
||||||
|
/// Tape changer (Autoloader, Robot)
|
||||||
|
Changer,
|
||||||
|
/// Normal SCSI tape device
|
||||||
|
Tape,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
kind: {
|
||||||
|
type: DeviceKind,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Debug,Serialize,Deserialize)]
|
||||||
|
/// Tape device information
|
||||||
|
pub struct TapeDeviceInfo {
|
||||||
|
pub kind: DeviceKind,
|
||||||
|
/// Path to the linux device node
|
||||||
|
pub path: String,
|
||||||
|
/// Serial number (autodetected)
|
||||||
|
pub serial: String,
|
||||||
|
/// Vendor (autodetected)
|
||||||
|
pub vendor: String,
|
||||||
|
/// Model (autodetected)
|
||||||
|
pub model: String,
|
||||||
|
/// Device major number
|
||||||
|
pub major: u32,
|
||||||
|
/// Device minor number
|
||||||
|
pub minor: u32,
|
||||||
|
}
|
285
pbs-api-types/src/tape/drive.rs
Normal file
285
pbs-api-types/src/tape/drive.rs
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
//! Types for tape drive API
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use anyhow::{bail, Error};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use proxmox::api::{
|
||||||
|
api,
|
||||||
|
schema::{Schema, IntegerSchema, StringSchema, Updater},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
PROXMOX_SAFE_ID_FORMAT,
|
||||||
|
CHANGER_NAME_SCHEMA,
|
||||||
|
OptionalDeviceIdentification,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DRIVE_NAME_SCHEMA: Schema = StringSchema::new("Drive Identifier.")
|
||||||
|
.format(&PROXMOX_SAFE_ID_FORMAT)
|
||||||
|
.min_length(3)
|
||||||
|
.max_length(32)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const LTO_DRIVE_PATH_SCHEMA: Schema = StringSchema::new(
|
||||||
|
"The path to a LTO SCSI-generic tape device (i.e. '/dev/sg0')")
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const CHANGER_DRIVENUM_SCHEMA: Schema = IntegerSchema::new(
|
||||||
|
"Associated changer drive number (requires option changer)")
|
||||||
|
.minimum(0)
|
||||||
|
.maximum(255)
|
||||||
|
.default(0)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
/// Simulate tape drives (only for test and debug)
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct VirtualTapeDrive {
|
||||||
|
pub name: String,
|
||||||
|
/// Path to directory
|
||||||
|
pub path: String,
|
||||||
|
/// Virtual tape size
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub max_size: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
|
},
|
||||||
|
changer: {
|
||||||
|
schema: CHANGER_NAME_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"changer-drivenum": {
|
||||||
|
schema: CHANGER_DRIVENUM_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize,Updater)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Lto SCSI tape driver
|
||||||
|
pub struct LtoTapeDrive {
|
||||||
|
#[updater(skip)]
|
||||||
|
pub name: String,
|
||||||
|
pub path: String,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub changer: Option<String>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub changer_drivenum: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
config: {
|
||||||
|
type: LtoTapeDrive,
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
type: OptionalDeviceIdentification,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Drive list entry
|
||||||
|
pub struct DriveListEntry {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub config: LtoTapeDrive,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub info: OptionalDeviceIdentification,
|
||||||
|
/// the state of the drive if locked
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub state: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api()]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
/// Medium auxiliary memory attributes (MAM)
|
||||||
|
pub struct MamAttribute {
|
||||||
|
/// Attribute id
|
||||||
|
pub id: u16,
|
||||||
|
/// Attribute name
|
||||||
|
pub name: String,
|
||||||
|
/// Attribute value
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api()]
|
||||||
|
#[derive(Serialize,Deserialize,Copy,Clone,Debug)]
|
||||||
|
pub enum TapeDensity {
|
||||||
|
/// Unknown (no media loaded)
|
||||||
|
Unknown,
|
||||||
|
/// LTO1
|
||||||
|
LTO1,
|
||||||
|
/// LTO2
|
||||||
|
LTO2,
|
||||||
|
/// LTO3
|
||||||
|
LTO3,
|
||||||
|
/// LTO4
|
||||||
|
LTO4,
|
||||||
|
/// LTO5
|
||||||
|
LTO5,
|
||||||
|
/// LTO6
|
||||||
|
LTO6,
|
||||||
|
/// LTO7
|
||||||
|
LTO7,
|
||||||
|
/// LTO7M8
|
||||||
|
LTO7M8,
|
||||||
|
/// LTO8
|
||||||
|
LTO8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for TapeDensity {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
let density = match value {
|
||||||
|
0x00 => TapeDensity::Unknown,
|
||||||
|
0x40 => TapeDensity::LTO1,
|
||||||
|
0x42 => TapeDensity::LTO2,
|
||||||
|
0x44 => TapeDensity::LTO3,
|
||||||
|
0x46 => TapeDensity::LTO4,
|
||||||
|
0x58 => TapeDensity::LTO5,
|
||||||
|
0x5a => TapeDensity::LTO6,
|
||||||
|
0x5c => TapeDensity::LTO7,
|
||||||
|
0x5d => TapeDensity::LTO7M8,
|
||||||
|
0x5e => TapeDensity::LTO8,
|
||||||
|
_ => bail!("unknown tape density code 0x{:02x}", value),
|
||||||
|
};
|
||||||
|
Ok(density)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
density: {
|
||||||
|
type: TapeDensity,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Drive/Media status for Lto SCSI drives.
|
||||||
|
///
|
||||||
|
/// Media related data is optional - only set if there is a medium
|
||||||
|
/// loaded.
|
||||||
|
pub struct LtoDriveAndMediaStatus {
|
||||||
|
/// Vendor
|
||||||
|
pub vendor: String,
|
||||||
|
/// Product
|
||||||
|
pub product: String,
|
||||||
|
/// Revision
|
||||||
|
pub revision: String,
|
||||||
|
/// Block size (0 is variable size)
|
||||||
|
pub blocksize: u32,
|
||||||
|
/// Compression enabled
|
||||||
|
pub compression: bool,
|
||||||
|
/// Drive buffer mode
|
||||||
|
pub buffer_mode: u8,
|
||||||
|
/// Tape density
|
||||||
|
pub density: TapeDensity,
|
||||||
|
/// Media is write protected
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub write_protect: Option<bool>,
|
||||||
|
/// Tape Alert Flags
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub alert_flags: Option<String>,
|
||||||
|
/// Current file number
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub file_number: Option<u64>,
|
||||||
|
/// Current block number
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub block_number: Option<u64>,
|
||||||
|
/// Medium Manufacture Date (epoch)
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub manufactured: Option<i64>,
|
||||||
|
/// Total Bytes Read in Medium Life
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub bytes_read: Option<u64>,
|
||||||
|
/// Total Bytes Written in Medium Life
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub bytes_written: Option<u64>,
|
||||||
|
/// Number of mounts for the current volume (i.e., Thread Count)
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub volume_mounts: Option<u64>,
|
||||||
|
/// Count of the total number of times the medium has passed over
|
||||||
|
/// the head.
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub medium_passes: Option<u64>,
|
||||||
|
/// Estimated tape wearout factor (assuming max. 16000 end-to-end passes)
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub medium_wearout: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api()]
|
||||||
|
/// Volume statistics from SCSI log page 17h
|
||||||
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct Lp17VolumeStatistics {
|
||||||
|
/// Volume mounts (thread count)
|
||||||
|
pub volume_mounts: u64,
|
||||||
|
/// Total data sets written
|
||||||
|
pub volume_datasets_written: u64,
|
||||||
|
/// Write retries
|
||||||
|
pub volume_recovered_write_data_errors: u64,
|
||||||
|
/// Total unrecovered write errors
|
||||||
|
pub volume_unrecovered_write_data_errors: u64,
|
||||||
|
/// Total suspended writes
|
||||||
|
pub volume_write_servo_errors: u64,
|
||||||
|
/// Total fatal suspended writes
|
||||||
|
pub volume_unrecovered_write_servo_errors: u64,
|
||||||
|
/// Total datasets read
|
||||||
|
pub volume_datasets_read: u64,
|
||||||
|
/// Total read retries
|
||||||
|
pub volume_recovered_read_errors: u64,
|
||||||
|
/// Total unrecovered read errors
|
||||||
|
pub volume_unrecovered_read_errors: u64,
|
||||||
|
/// Last mount unrecovered write errors
|
||||||
|
pub last_mount_unrecovered_write_errors: u64,
|
||||||
|
/// Last mount unrecovered read errors
|
||||||
|
pub last_mount_unrecovered_read_errors: u64,
|
||||||
|
/// Last mount bytes written
|
||||||
|
pub last_mount_bytes_written: u64,
|
||||||
|
/// Last mount bytes read
|
||||||
|
pub last_mount_bytes_read: u64,
|
||||||
|
/// Lifetime bytes written
|
||||||
|
pub lifetime_bytes_written: u64,
|
||||||
|
/// Lifetime bytes read
|
||||||
|
pub lifetime_bytes_read: u64,
|
||||||
|
/// Last load write compression ratio
|
||||||
|
pub last_load_write_compression_ratio: u64,
|
||||||
|
/// Last load read compression ratio
|
||||||
|
pub last_load_read_compression_ratio: u64,
|
||||||
|
/// Medium mount time
|
||||||
|
pub medium_mount_time: u64,
|
||||||
|
/// Medium ready time
|
||||||
|
pub medium_ready_time: u64,
|
||||||
|
/// Total native capacity
|
||||||
|
pub total_native_capacity: u64,
|
||||||
|
/// Total used native capacity
|
||||||
|
pub total_used_native_capacity: u64,
|
||||||
|
/// Write protect
|
||||||
|
pub write_protect: bool,
|
||||||
|
/// Volume is WORM
|
||||||
|
pub worm: bool,
|
||||||
|
/// Beginning of medium passes
|
||||||
|
pub beginning_of_medium_passes: u64,
|
||||||
|
/// Middle of medium passes
|
||||||
|
pub middle_of_tape_passes: u64,
|
||||||
|
/// Volume serial number
|
||||||
|
pub serial: String,
|
||||||
|
}
|
182
pbs-api-types/src/tape/media.rs
Normal file
182
pbs-api-types/src/tape/media.rs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
use ::serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use proxmox::{
|
||||||
|
api::{api, schema::*},
|
||||||
|
tools::Uuid,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
UUID_FORMAT,
|
||||||
|
MediaStatus,
|
||||||
|
MediaLocation,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MEDIA_SET_UUID_SCHEMA: Schema =
|
||||||
|
StringSchema::new("MediaSet Uuid (We use the all-zero Uuid to reseve an empty media for a specific pool).")
|
||||||
|
.format(&UUID_FORMAT)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const MEDIA_UUID_SCHEMA: Schema =
|
||||||
|
StringSchema::new("Media Uuid.")
|
||||||
|
.format(&UUID_FORMAT)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
"media-set-uuid": {
|
||||||
|
schema: MEDIA_SET_UUID_SCHEMA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Media Set list entry
|
||||||
|
pub struct MediaSetListEntry {
|
||||||
|
/// Media set name
|
||||||
|
pub media_set_name: String,
|
||||||
|
pub media_set_uuid: Uuid,
|
||||||
|
/// MediaSet creation time stamp
|
||||||
|
pub media_set_ctime: i64,
|
||||||
|
/// Media Pool
|
||||||
|
pub pool: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
location: {
|
||||||
|
type: MediaLocation,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: MediaStatus,
|
||||||
|
},
|
||||||
|
uuid: {
|
||||||
|
schema: MEDIA_UUID_SCHEMA,
|
||||||
|
},
|
||||||
|
"media-set-uuid": {
|
||||||
|
schema: MEDIA_SET_UUID_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Media list entry
|
||||||
|
pub struct MediaListEntry {
|
||||||
|
/// Media label text (or Barcode)
|
||||||
|
pub label_text: String,
|
||||||
|
pub uuid: Uuid,
|
||||||
|
/// Creation time stamp
|
||||||
|
pub ctime: i64,
|
||||||
|
pub location: MediaLocation,
|
||||||
|
pub status: MediaStatus,
|
||||||
|
/// Expired flag
|
||||||
|
pub expired: bool,
|
||||||
|
/// Catalog status OK
|
||||||
|
pub catalog: bool,
|
||||||
|
/// Media set name
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub media_set_name: Option<String>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub media_set_uuid: Option<Uuid>,
|
||||||
|
/// Media set seq_nr
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub seq_nr: Option<u64>,
|
||||||
|
/// MediaSet creation time stamp
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub media_set_ctime: Option<i64>,
|
||||||
|
/// Media Pool
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub pool: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
uuid: {
|
||||||
|
schema: MEDIA_UUID_SCHEMA,
|
||||||
|
},
|
||||||
|
"media-set-uuid": {
|
||||||
|
schema: MEDIA_SET_UUID_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Media label info
|
||||||
|
pub struct MediaIdFlat {
|
||||||
|
/// Unique ID
|
||||||
|
pub uuid: Uuid,
|
||||||
|
/// Media label text (or Barcode)
|
||||||
|
pub label_text: String,
|
||||||
|
/// Creation time stamp
|
||||||
|
pub ctime: i64,
|
||||||
|
// All MediaSet properties are optional here
|
||||||
|
/// MediaSet Pool
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub pool: Option<String>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub media_set_uuid: Option<Uuid>,
|
||||||
|
/// MediaSet media sequence number
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub seq_nr: Option<u64>,
|
||||||
|
/// MediaSet Creation time stamp
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub media_set_ctime: Option<i64>,
|
||||||
|
/// Encryption key fingerprint
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub encryption_key_fingerprint: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
uuid: {
|
||||||
|
schema: MEDIA_UUID_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Label with optional Uuid
|
||||||
|
pub struct LabelUuidMap {
|
||||||
|
/// Changer label text (or Barcode)
|
||||||
|
pub label_text: String,
|
||||||
|
/// Associated Uuid (if any)
|
||||||
|
pub uuid: Option<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
uuid: {
|
||||||
|
schema: MEDIA_UUID_SCHEMA,
|
||||||
|
},
|
||||||
|
"media-set-uuid": {
|
||||||
|
schema: MEDIA_SET_UUID_SCHEMA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
/// Media content list entry
|
||||||
|
pub struct MediaContentEntry {
|
||||||
|
/// Media label text (or Barcode)
|
||||||
|
pub label_text: String,
|
||||||
|
/// Media Uuid
|
||||||
|
pub uuid: Uuid,
|
||||||
|
/// Media set name
|
||||||
|
pub media_set_name: String,
|
||||||
|
/// Media set uuid
|
||||||
|
pub media_set_uuid: Uuid,
|
||||||
|
/// MediaSet Creation time stamp
|
||||||
|
pub media_set_ctime: i64,
|
||||||
|
/// Media set seq_nr
|
||||||
|
pub seq_nr: u64,
|
||||||
|
/// Media Pool
|
||||||
|
pub pool: String,
|
||||||
|
/// Datastore Name
|
||||||
|
pub store: String,
|
||||||
|
/// Backup snapshot
|
||||||
|
pub snapshot: String,
|
||||||
|
/// Snapshot creation time (epoch)
|
||||||
|
pub backup_time: i64,
|
||||||
|
}
|
91
pbs-api-types/src/tape/media_location.rs
Normal file
91
pbs-api-types/src/tape/media_location.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use anyhow::{bail, Error};
|
||||||
|
|
||||||
|
use proxmox::api::{
|
||||||
|
schema::{
|
||||||
|
Schema,
|
||||||
|
StringSchema,
|
||||||
|
ApiStringFormat,
|
||||||
|
parse_simple_value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
PROXMOX_SAFE_ID_FORMAT,
|
||||||
|
CHANGER_NAME_SCHEMA,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const VAULT_NAME_SCHEMA: Schema = StringSchema::new("Vault name.")
|
||||||
|
.format(&PROXMOX_SAFE_ID_FORMAT)
|
||||||
|
.min_length(3)
|
||||||
|
.max_length(32)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
/// Media location
|
||||||
|
pub enum MediaLocation {
|
||||||
|
/// Ready for use (inside tape library)
|
||||||
|
Online(String),
|
||||||
|
/// Local available, but need to be mounted (insert into tape
|
||||||
|
/// drive)
|
||||||
|
Offline,
|
||||||
|
/// Media is inside a Vault
|
||||||
|
Vault(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
proxmox::forward_deserialize_to_from_str!(MediaLocation);
|
||||||
|
proxmox::forward_serialize_to_display!(MediaLocation);
|
||||||
|
|
||||||
|
impl proxmox::api::schema::ApiType for MediaLocation {
|
||||||
|
const API_SCHEMA: Schema = StringSchema::new(
|
||||||
|
"Media location (e.g. 'offline', 'online-<changer_name>', 'vault-<vault_name>')")
|
||||||
|
.format(&ApiStringFormat::VerifyFn(|text| {
|
||||||
|
let location: MediaLocation = text.parse()?;
|
||||||
|
match location {
|
||||||
|
MediaLocation::Online(ref changer) => {
|
||||||
|
parse_simple_value(changer, &CHANGER_NAME_SCHEMA)?;
|
||||||
|
}
|
||||||
|
MediaLocation::Vault(ref vault) => {
|
||||||
|
parse_simple_value(vault, &VAULT_NAME_SCHEMA)?;
|
||||||
|
}
|
||||||
|
MediaLocation::Offline => { /* OK */}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
.schema();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl std::fmt::Display for MediaLocation {
|
||||||
|
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
MediaLocation::Offline => {
|
||||||
|
write!(f, "offline")
|
||||||
|
}
|
||||||
|
MediaLocation::Online(changer) => {
|
||||||
|
write!(f, "online-{}", changer)
|
||||||
|
}
|
||||||
|
MediaLocation::Vault(vault) => {
|
||||||
|
write!(f, "vault-{}", vault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for MediaLocation {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s == "offline" {
|
||||||
|
return Ok(MediaLocation::Offline);
|
||||||
|
}
|
||||||
|
if let Some(changer) = s.strip_prefix("online-") {
|
||||||
|
return Ok(MediaLocation::Online(changer.to_string()));
|
||||||
|
}
|
||||||
|
if let Some(vault) = s.strip_prefix("vault-") {
|
||||||
|
return Ok(MediaLocation::Vault(vault.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("MediaLocation parse error");
|
||||||
|
}
|
||||||
|
}
|
161
pbs-api-types/src/tape/media_pool.rs
Normal file
161
pbs-api-types/src/tape/media_pool.rs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
//! Types for tape media pool API
|
||||||
|
//!
|
||||||
|
//! Note: Both MediaSetPolicy and RetentionPolicy are complex enums,
|
||||||
|
//! so we cannot use them directly for the API. Instead, we represent
|
||||||
|
//! them as String.
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use proxmox::api::{
|
||||||
|
api,
|
||||||
|
schema::{Schema, StringSchema, ApiStringFormat, Updater},
|
||||||
|
};
|
||||||
|
|
||||||
|
use pbs_systemd::time::{parse_calendar_event, parse_time_span, CalendarEvent, TimeSpan};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
PROXMOX_SAFE_ID_FORMAT,
|
||||||
|
SINGLE_LINE_COMMENT_FORMAT,
|
||||||
|
SINGLE_LINE_COMMENT_SCHEMA,
|
||||||
|
TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MEDIA_POOL_NAME_SCHEMA: Schema = StringSchema::new("Media pool name.")
|
||||||
|
.format(&PROXMOX_SAFE_ID_FORMAT)
|
||||||
|
.min_length(2)
|
||||||
|
.max_length(32)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const MEDIA_SET_NAMING_TEMPLATE_SCHEMA: Schema = StringSchema::new(
|
||||||
|
"Media set naming template (may contain strftime() time format specifications).")
|
||||||
|
.format(&SINGLE_LINE_COMMENT_FORMAT)
|
||||||
|
.min_length(2)
|
||||||
|
.max_length(64)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const MEDIA_SET_ALLOCATION_POLICY_FORMAT: ApiStringFormat =
|
||||||
|
ApiStringFormat::VerifyFn(|s| { MediaSetPolicy::from_str(s)?; Ok(()) });
|
||||||
|
|
||||||
|
pub const MEDIA_SET_ALLOCATION_POLICY_SCHEMA: Schema = StringSchema::new(
|
||||||
|
"Media set allocation policy ('continue', 'always', or a calendar event).")
|
||||||
|
.format(&MEDIA_SET_ALLOCATION_POLICY_FORMAT)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
/// Media set allocation policy
|
||||||
|
pub enum MediaSetPolicy {
|
||||||
|
/// Try to use the current media set
|
||||||
|
ContinueCurrent,
|
||||||
|
/// Each backup job creates a new media set
|
||||||
|
AlwaysCreate,
|
||||||
|
/// Create a new set when the specified CalendarEvent triggers
|
||||||
|
CreateAt(CalendarEvent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for MediaSetPolicy {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s == "continue" {
|
||||||
|
return Ok(MediaSetPolicy::ContinueCurrent);
|
||||||
|
}
|
||||||
|
if s == "always" {
|
||||||
|
return Ok(MediaSetPolicy::AlwaysCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
let event = parse_calendar_event(s)?;
|
||||||
|
|
||||||
|
Ok(MediaSetPolicy::CreateAt(event))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const MEDIA_RETENTION_POLICY_FORMAT: ApiStringFormat =
|
||||||
|
ApiStringFormat::VerifyFn(|s| { RetentionPolicy::from_str(s)?; Ok(()) });
|
||||||
|
|
||||||
|
pub const MEDIA_RETENTION_POLICY_SCHEMA: Schema = StringSchema::new(
|
||||||
|
"Media retention policy ('overwrite', 'keep', or time span).")
|
||||||
|
.format(&MEDIA_RETENTION_POLICY_FORMAT)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
/// Media retention Policy
|
||||||
|
pub enum RetentionPolicy {
|
||||||
|
/// Always overwrite media
|
||||||
|
OverwriteAlways,
|
||||||
|
/// Protect data for the timespan specified
|
||||||
|
ProtectFor(TimeSpan),
|
||||||
|
/// Never overwrite data
|
||||||
|
KeepForever,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for RetentionPolicy {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s == "overwrite" {
|
||||||
|
return Ok(RetentionPolicy::OverwriteAlways);
|
||||||
|
}
|
||||||
|
if s == "keep" {
|
||||||
|
return Ok(RetentionPolicy::KeepForever);
|
||||||
|
}
|
||||||
|
|
||||||
|
let time_span = parse_time_span(s)?;
|
||||||
|
|
||||||
|
Ok(RetentionPolicy::ProtectFor(time_span))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
schema: MEDIA_POOL_NAME_SCHEMA,
|
||||||
|
},
|
||||||
|
allocation: {
|
||||||
|
schema: MEDIA_SET_ALLOCATION_POLICY_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
retention: {
|
||||||
|
schema: MEDIA_RETENTION_POLICY_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
schema: MEDIA_SET_NAMING_TEMPLATE_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
encrypt: {
|
||||||
|
schema: TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
comment: {
|
||||||
|
optional: true,
|
||||||
|
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize,Updater)]
|
||||||
|
/// Media pool configuration
|
||||||
|
pub struct MediaPoolConfig {
|
||||||
|
/// The pool name
|
||||||
|
#[updater(skip)]
|
||||||
|
pub name: String,
|
||||||
|
/// Media Set allocation policy
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub allocation: Option<String>,
|
||||||
|
/// Media retention policy
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub retention: Option<String>,
|
||||||
|
/// Media set naming template (default "%c")
|
||||||
|
///
|
||||||
|
/// The template is UTF8 text, and can include strftime time
|
||||||
|
/// format specifications.
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub template: Option<String>,
|
||||||
|
/// Encryption key fingerprint
|
||||||
|
///
|
||||||
|
/// If set, encrypt all data using the specified key.
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub encrypt: Option<String>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub comment: Option<String>,
|
||||||
|
}
|
21
pbs-api-types/src/tape/media_status.rs
Normal file
21
pbs-api-types/src/tape/media_status.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use ::serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use proxmox::api::api;
|
||||||
|
|
||||||
|
#[api()]
|
||||||
|
/// Media status
|
||||||
|
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
/// Media Status
|
||||||
|
pub enum MediaStatus {
|
||||||
|
/// Media is ready to be written
|
||||||
|
Writable,
|
||||||
|
/// Media is full (contains data)
|
||||||
|
Full,
|
||||||
|
/// Media is marked as unknown, needs rescan
|
||||||
|
Unknown,
|
||||||
|
/// Media is marked as damaged
|
||||||
|
Damaged,
|
||||||
|
/// Media is marked as retired
|
||||||
|
Retired,
|
||||||
|
}
|
94
pbs-api-types/src/tape/mod.rs
Normal file
94
pbs-api-types/src/tape/mod.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
//! Types for tape backup API
|
||||||
|
|
||||||
|
mod device;
|
||||||
|
pub use device::*;
|
||||||
|
|
||||||
|
mod changer;
|
||||||
|
pub use changer::*;
|
||||||
|
|
||||||
|
mod drive;
|
||||||
|
pub use drive::*;
|
||||||
|
|
||||||
|
mod media_pool;
|
||||||
|
pub use media_pool::*;
|
||||||
|
|
||||||
|
mod media_status;
|
||||||
|
pub use media_status::*;
|
||||||
|
|
||||||
|
mod media_location;
|
||||||
|
|
||||||
|
pub use media_location::*;
|
||||||
|
|
||||||
|
mod media;
|
||||||
|
pub use media::*;
|
||||||
|
|
||||||
|
use ::serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use proxmox::api::api;
|
||||||
|
use proxmox::api::schema::{Schema, StringSchema, ApiStringFormat};
|
||||||
|
use proxmox::tools::Uuid;
|
||||||
|
|
||||||
|
use proxmox::const_regex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
FINGERPRINT_SHA256_FORMAT, BACKUP_ID_SCHEMA, BACKUP_TYPE_SCHEMA,
|
||||||
|
};
|
||||||
|
|
||||||
|
const_regex!{
|
||||||
|
pub TAPE_RESTORE_SNAPSHOT_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r":", SNAPSHOT_PATH_REGEX_STR!(), r"$");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const TAPE_RESTORE_SNAPSHOT_FORMAT: ApiStringFormat =
|
||||||
|
ApiStringFormat::Pattern(&TAPE_RESTORE_SNAPSHOT_REGEX);
|
||||||
|
|
||||||
|
pub const TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA: Schema = StringSchema::new(
|
||||||
|
"Tape encryption key fingerprint (sha256)."
|
||||||
|
)
|
||||||
|
.format(&FINGERPRINT_SHA256_FORMAT)
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
pub const TAPE_RESTORE_SNAPSHOT_SCHEMA: Schema = StringSchema::new(
|
||||||
|
"A snapshot in the format: 'store:type/id/time")
|
||||||
|
.format(&TAPE_RESTORE_SNAPSHOT_FORMAT)
|
||||||
|
.type_text("store:type/id/time")
|
||||||
|
.schema();
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
properties: {
|
||||||
|
pool: {
|
||||||
|
schema: MEDIA_POOL_NAME_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"label-text": {
|
||||||
|
schema: MEDIA_LABEL_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
schema: MEDIA_UUID_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"media-set": {
|
||||||
|
schema: MEDIA_SET_UUID_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"backup-type": {
|
||||||
|
schema: BACKUP_TYPE_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"backup-id": {
|
||||||
|
schema: BACKUP_ID_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
#[serde(rename_all="kebab-case")]
|
||||||
|
/// Content list filter parameters
|
||||||
|
pub struct MediaContentListFilter {
|
||||||
|
pub pool: Option<String>,
|
||||||
|
pub label_text: Option<String>,
|
||||||
|
pub media: Option<Uuid>,
|
||||||
|
pub media_set: Option<Uuid>,
|
||||||
|
pub backup_type: Option<String>,
|
||||||
|
pub backup_id: Option<String>,
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user