mangleframes 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mangleframes-0.3.0 → mangleframes-0.3.1}/PKG-INFO +1 -1
- {mangleframes-0.3.0 → mangleframes-0.3.1}/pyproject.toml +1 -1
- {mangleframes-0.3.0 → mangleframes-0.3.1}/python/mangleframes/__init__.py +1 -1
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/Cargo.toml +1 -1
- mangleframes-0.3.1/spark-connect/src/lib.rs +67 -0
- mangleframes-0.3.1/viewer/frontend/src/lib/api.ts +174 -0
- mangleframes-0.3.0/spark-connect/src/lib.rs +0 -15
- {mangleframes-0.3.0 → mangleframes-0.3.1}/Cargo.lock +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/Cargo.toml +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/python/mangleframes/alerts.py +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/python/mangleframes/launcher.py +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/python/mangleframes/session.py +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/build.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/base.proto +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/catalog.proto +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/commands.proto +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/common.proto +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/expressions.proto +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/ml.proto +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/ml_common.proto +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/relations.proto +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/types.proto +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/src/client.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/src/error.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/src/proto/spark.connect.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/Cargo.toml +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/index.html +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/package-lock.json +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/package.json +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/postcss.config.js +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/App.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/analysis/JoinAnalyzer.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/analysis/Reconciliation.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/analysis/SQLEditor.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/ColumnDropdown.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/ColumnStats.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/DataGrid.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/SchemaView.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/ContextPanel.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/Layout.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/MainContent.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/Sidebar.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/StatusBar.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/TabBar.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/TopBar.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/quality/AlertBuilder.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/quality/QualityDashboard.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/index.css +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/main.tsx +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/stores/dataStore.ts +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/stores/uiStore.ts +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/tailwind.config.js +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/tsconfig.json +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/tsconfig.node.json +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/vite.config.ts +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/alert_handlers.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/arrow_reader.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/dashboard.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/export.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/handlers.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/history_analysis.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/history_handlers.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/join_handlers.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/main.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/perf.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/reconcile_handlers.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/spark_client.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/sql_builder.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/stats.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/test_helpers.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/web_server.rs +0 -0
- {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/websocket.rs +0 -0
|
@@ -6,7 +6,7 @@ description = "Rust client for Apache Spark Connect gRPC protocol"
|
|
|
6
6
|
|
|
7
7
|
[dependencies]
|
|
8
8
|
tonic = { version = "0.12", features = ["tls", "tls-roots", "gzip"] }
|
|
9
|
-
prost = "0.13"
|
|
9
|
+
prost = { version = "0.13", features = ["no-recursion-limit"] }
|
|
10
10
|
prost-types = "0.13"
|
|
11
11
|
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
|
12
12
|
arrow = { version = "54", features = ["ipc"] }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//! Spark Connect Rust client for Apache Spark gRPC protocol.
|
|
2
|
+
//!
|
|
3
|
+
//! This crate provides a Rust client for connecting to Spark clusters via the
|
|
4
|
+
//! Spark Connect gRPC protocol, with specific support for Databricks.
|
|
5
|
+
|
|
6
|
+
pub mod client;
|
|
7
|
+
pub mod error;
|
|
8
|
+
|
|
9
|
+
#[allow(clippy::all)]
|
|
10
|
+
mod proto {
|
|
11
|
+
include!("proto/spark.connect.rs");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub use client::{ColumnSchema, SparkConnectClient};
|
|
15
|
+
pub use error::SparkConnectError;
|
|
16
|
+
|
|
17
|
+
#[cfg(test)]
|
|
18
|
+
mod tests {
|
|
19
|
+
use super::proto::{relation, Limit, Relation};
|
|
20
|
+
use prost::Message;
|
|
21
|
+
|
|
22
|
+
/// Build a deeply nested Relation with `depth` levels of Limit operations.
|
|
23
|
+
fn build_nested_relation(depth: usize) -> Relation {
|
|
24
|
+
let mut current = Relation {
|
|
25
|
+
common: None,
|
|
26
|
+
rel_type: None,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
for i in 0..depth {
|
|
30
|
+
current = Relation {
|
|
31
|
+
common: None,
|
|
32
|
+
rel_type: Some(relation::RelType::Limit(Box::new(Limit {
|
|
33
|
+
input: Some(Box::new(current)),
|
|
34
|
+
limit: i as i32,
|
|
35
|
+
}))),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
current
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Verifies the no-recursion-limit feature works by encoding/decoding
|
|
43
|
+
/// a deeply nested protobuf message (150+ levels exceeds old 100 limit).
|
|
44
|
+
#[test]
|
|
45
|
+
fn test_deeply_nested_relation_decodes() {
|
|
46
|
+
let depth = 150;
|
|
47
|
+
let relation = build_nested_relation(depth);
|
|
48
|
+
|
|
49
|
+
let encoded = relation.encode_to_vec();
|
|
50
|
+
let decoded = Relation::decode(encoded.as_slice())
|
|
51
|
+
.expect("should decode deeply nested relation without recursion limit");
|
|
52
|
+
|
|
53
|
+
// Verify the structure by walking down to confirm depth
|
|
54
|
+
let mut count = 0;
|
|
55
|
+
let mut current = &decoded;
|
|
56
|
+
while let Some(relation::RelType::Limit(limit)) = ¤t.rel_type {
|
|
57
|
+
count += 1;
|
|
58
|
+
if let Some(input) = &limit.input {
|
|
59
|
+
current = input;
|
|
60
|
+
} else {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
assert_eq!(count, depth, "decoded relation should have correct nesting depth");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
const BASE_URL = ''
|
|
2
|
+
|
|
3
|
+
export interface Column {
|
|
4
|
+
name: string
|
|
5
|
+
type: string
|
|
6
|
+
nullable: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface Timing {
|
|
10
|
+
spark_ms: number
|
|
11
|
+
ipc_ms: number
|
|
12
|
+
json_ms: number
|
|
13
|
+
total_ms: number
|
|
14
|
+
cached: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DataResponse {
|
|
18
|
+
rows: Record<string, unknown>[]
|
|
19
|
+
total: number
|
|
20
|
+
offset: number
|
|
21
|
+
timing: Timing
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SchemaResponse {
|
|
25
|
+
columns: Column[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FramesResponse {
|
|
29
|
+
frames: string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface StatsResponse {
|
|
33
|
+
row_count: number
|
|
34
|
+
columns: ColumnStats[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ColumnStats {
|
|
38
|
+
name: string
|
|
39
|
+
type: string
|
|
40
|
+
null_count: number
|
|
41
|
+
distinct_count?: number
|
|
42
|
+
min?: string | number
|
|
43
|
+
max?: string | number
|
|
44
|
+
avg?: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface QueryResponse {
|
|
48
|
+
rows: Record<string, unknown>[]
|
|
49
|
+
total: number
|
|
50
|
+
timing: { execution_ms: number }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface CsvUploadResponse {
|
|
54
|
+
frame_name: string
|
|
55
|
+
columns: { name: string; data_type: string; nullable: boolean }[]
|
|
56
|
+
row_count: number
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface CsvFramesResponse {
|
|
60
|
+
frames: string[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ReconcileStatistics {
|
|
64
|
+
match_rate: number
|
|
65
|
+
matched_groups: number
|
|
66
|
+
source_groups: number
|
|
67
|
+
source_only_groups: number
|
|
68
|
+
target_groups: number
|
|
69
|
+
target_only_groups: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ColumnTotal {
|
|
73
|
+
column: string
|
|
74
|
+
aggregation: string
|
|
75
|
+
source_total: number
|
|
76
|
+
target_total: number
|
|
77
|
+
difference: number
|
|
78
|
+
percent_diff: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ReconcileResponse {
|
|
82
|
+
statistics: ReconcileStatistics
|
|
83
|
+
column_totals: ColumnTotal[]
|
|
84
|
+
source_only: { total: number; rows: Record<string, unknown>[] }
|
|
85
|
+
target_only: { total: number; rows: Record<string, unknown>[] }
|
|
86
|
+
matched_rows: { rows: Record<string, unknown>[] }
|
|
87
|
+
source_frame: string
|
|
88
|
+
target_frame: string
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ReconcileRequest {
|
|
92
|
+
source_frame: string
|
|
93
|
+
target_frame: string
|
|
94
|
+
source_type?: string
|
|
95
|
+
source_group_by: string[]
|
|
96
|
+
target_group_by: string[]
|
|
97
|
+
source_join_keys: string[]
|
|
98
|
+
target_join_keys: string[]
|
|
99
|
+
join_type: 'inner' | 'left' | 'right' | 'full'
|
|
100
|
+
aggregations: { column: string; aggregations: string[] }[]
|
|
101
|
+
sample_limit?: number
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function request<T>(path: string, options?: RequestInit): Promise<T> {
|
|
105
|
+
const response = await fetch(`${BASE_URL}${path}`, {
|
|
106
|
+
headers: { 'Content-Type': 'application/json' },
|
|
107
|
+
...options,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
const error = await response.json().catch(() => ({ error: response.statusText }))
|
|
112
|
+
throw new Error(error.error || `Request failed: ${response.status}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return response.json()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const api = {
|
|
119
|
+
listFrames: () => request<FramesResponse>('/api/frames'),
|
|
120
|
+
|
|
121
|
+
getSchema: (name: string) =>
|
|
122
|
+
request<SchemaResponse>(`/api/frames/${encodeURIComponent(name)}/schema`),
|
|
123
|
+
|
|
124
|
+
getData: (name: string, offset = 0, limit = 100) =>
|
|
125
|
+
request<DataResponse>(
|
|
126
|
+
`/api/frames/${encodeURIComponent(name)}/data?offset=${offset}&limit=${limit}`
|
|
127
|
+
),
|
|
128
|
+
|
|
129
|
+
getStats: (name: string) =>
|
|
130
|
+
request<StatsResponse>(`/api/frames/${encodeURIComponent(name)}/stats`),
|
|
131
|
+
|
|
132
|
+
executeQuery: (sql: string) =>
|
|
133
|
+
request<QueryResponse>('/api/query', {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
body: JSON.stringify({ sql }),
|
|
136
|
+
}),
|
|
137
|
+
|
|
138
|
+
exportFrame: (name: string, format: 'csv' | 'json' | 'parquet') =>
|
|
139
|
+
fetch(`${BASE_URL}/api/frames/${encodeURIComponent(name)}/export`, {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: { 'Content-Type': 'application/json' },
|
|
142
|
+
body: JSON.stringify({ format }),
|
|
143
|
+
}),
|
|
144
|
+
|
|
145
|
+
uploadCsv: async (file: File, frameName: string): Promise<CsvUploadResponse> => {
|
|
146
|
+
const formData = new FormData()
|
|
147
|
+
formData.append('file', file)
|
|
148
|
+
formData.append('frame_name', frameName)
|
|
149
|
+
const response = await fetch(`${BASE_URL}/api/reconcile/upload`, {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
body: formData,
|
|
152
|
+
})
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
const error = await response.json().catch(() => ({ error: response.statusText }))
|
|
155
|
+
throw new Error(error.error || `Upload failed: ${response.status}`)
|
|
156
|
+
}
|
|
157
|
+
return response.json()
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
listCsvFrames: () => request<CsvFramesResponse>('/api/reconcile/frames'),
|
|
161
|
+
|
|
162
|
+
reconcile: (req: ReconcileRequest) =>
|
|
163
|
+
request<ReconcileResponse>('/api/reconcile', {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
body: JSON.stringify(req),
|
|
166
|
+
}),
|
|
167
|
+
|
|
168
|
+
exportReconciliation: (req: ReconcileRequest) =>
|
|
169
|
+
fetch(`${BASE_URL}/api/reconcile/export`, {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: { 'Content-Type': 'application/json' },
|
|
172
|
+
body: JSON.stringify(req),
|
|
173
|
+
}),
|
|
174
|
+
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
//! Spark Connect Rust client for Apache Spark gRPC protocol.
|
|
2
|
-
//!
|
|
3
|
-
//! This crate provides a Rust client for connecting to Spark clusters via the
|
|
4
|
-
//! Spark Connect gRPC protocol, with specific support for Databricks.
|
|
5
|
-
|
|
6
|
-
pub mod client;
|
|
7
|
-
pub mod error;
|
|
8
|
-
|
|
9
|
-
#[allow(clippy::all)]
|
|
10
|
-
mod proto {
|
|
11
|
-
include!("proto/spark.connect.rs");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
pub use client::{ColumnSchema, SparkConnectClient};
|
|
15
|
-
pub use error::SparkConnectError;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/expressions.proto
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/analysis/JoinAnalyzer.tsx
RENAMED
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/analysis/Reconciliation.tsx
RENAMED
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/analysis/SQLEditor.tsx
RENAMED
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/ColumnDropdown.tsx
RENAMED
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/ColumnStats.tsx
RENAMED
|
File without changes
|
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/SchemaView.tsx
RENAMED
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/ContextPanel.tsx
RENAMED
|
File without changes
|
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/MainContent.tsx
RENAMED
|
File without changes
|
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/StatusBar.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/quality/AlertBuilder.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|