409 lines
12 KiB
Dart
409 lines
12 KiB
Dart
import 'package:data_table_2/data_table_2.dart';
|
||
import 'package:dio/dio.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart' hide Response;
|
||
import 'package:recase/recase.dart';
|
||
import 'package:styled_widget/styled_widget.dart';
|
||
import 'package:tuuli_api/tuuli_api.dart';
|
||
import 'package:tuuli_app/api_controller.dart';
|
||
import 'package:tuuli_app/models/group_definition.dart';
|
||
import 'package:tuuli_app/models/table_access.dart';
|
||
|
||
class GroupACLController extends GetxController {
|
||
final GroupDefinition group;
|
||
|
||
GroupACLController(this.group);
|
||
|
||
@override
|
||
void onInit() {
|
||
super.onInit();
|
||
|
||
refreshData();
|
||
}
|
||
|
||
final _tables = <TableDefinition>[].obs;
|
||
List<TableDefinition> get tables => _tables.toList();
|
||
|
||
final _access = <String, TableAccess>{}.obs;
|
||
Map<String, TableAccess> get access => _access;
|
||
|
||
final _allowedColumns = <String, String?>{}.obs;
|
||
Map<String, String?> get allowedColumns => _allowedColumns;
|
||
|
||
Future<void> refreshData() async {
|
||
await refreshTables();
|
||
for (final table in tables) {
|
||
_access[table.tableId] = TableAccess.none;
|
||
}
|
||
|
||
for (final table in tables) {
|
||
await refreshTableAccess(table);
|
||
}
|
||
}
|
||
|
||
Future<void> refreshTables() async {
|
||
try {
|
||
final resp = await ApiController.to.apiClient.listTables();
|
||
|
||
final respData = resp.data;
|
||
if (respData == null) {
|
||
throw Exception("В ответе нет данных");
|
||
}
|
||
|
||
_tables.clear();
|
||
_tables.addAll(respData.where((e) => e.hidden != true));
|
||
} on DioError catch (e) {
|
||
final respData = e.response?.data;
|
||
if (respData != null) {
|
||
Get.snackbar(
|
||
"Ошибка запроса таблиц",
|
||
"${respData['error']}",
|
||
);
|
||
} else {
|
||
Get.snackbar(
|
||
"Ошибка запроса таблиц",
|
||
"$e",
|
||
);
|
||
}
|
||
} catch (e) {
|
||
Get.snackbar(
|
||
"Ошибка запроса таблиц",
|
||
"$e",
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> refreshTableAccess(TableDefinition table) async {
|
||
try {
|
||
final resp = await ApiController.to.apiClient.getItemsFromTable(
|
||
tableName: "table_access",
|
||
itemsSelector: ItemsSelector(
|
||
fields: ["access_type", "allowed_columns"],
|
||
where: [
|
||
ColumnConditionCompat(
|
||
column: "user_group_id",
|
||
operator_: ColumnConditionCompatOperator.eq,
|
||
value: group.id,
|
||
),
|
||
ColumnConditionCompat(
|
||
column: "table_name",
|
||
operator_: ColumnConditionCompatOperator.eq,
|
||
value: table.tableName,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
final respData = resp.data;
|
||
if (respData == null) {
|
||
throw Exception("В ответе нет данных");
|
||
}
|
||
|
||
if (respData.isNotEmpty) {
|
||
_access[table.tableId] =
|
||
TableAccess.fromString(respData.first["access_type"]);
|
||
_allowedColumns[table.tableId] = respData.first["allowed_columns"];
|
||
} else {
|
||
_access[table.tableId] = TableAccess.none;
|
||
_allowedColumns[table.tableId] = null;
|
||
}
|
||
} on DioError catch (e) {
|
||
final respData = e.response?.data;
|
||
if (respData != null) {
|
||
Get.snackbar(
|
||
"Ошибка запроса информации доступа к таблицам",
|
||
"${respData['error']}",
|
||
);
|
||
} else {
|
||
Get.snackbar(
|
||
"Ошибка запроса информации доступа к таблицам",
|
||
"$e",
|
||
);
|
||
}
|
||
} catch (e) {
|
||
Get.snackbar(
|
||
"Ошибка запроса информации доступа к таблицам",
|
||
"$e",
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> updateTableAccess(
|
||
TableDefinition table, {
|
||
required bool read,
|
||
required bool write,
|
||
}) async {
|
||
final oldTableAccess = _access[table.tableId];
|
||
var newTableAccess = TableAccess.none;
|
||
if (read && write) {
|
||
newTableAccess = TableAccess.readWrite;
|
||
} else if (read) {
|
||
newTableAccess = TableAccess.read;
|
||
} else if (write) {
|
||
newTableAccess = TableAccess.write;
|
||
}
|
||
|
||
try {
|
||
Response<OkResponse> resp;
|
||
if (newTableAccess == TableAccess.none) {
|
||
resp = await ApiController.to.apiClient.deleteItemFromTable(
|
||
tableName: "table_access",
|
||
columnConditionCompat: [
|
||
ColumnConditionCompat(
|
||
column: "user_group_id",
|
||
operator_: ColumnConditionCompatOperator.eq,
|
||
value: group.id,
|
||
),
|
||
ColumnConditionCompat(
|
||
column: "table_name",
|
||
operator_: ColumnConditionCompatOperator.eq,
|
||
value: table.tableName,
|
||
),
|
||
],
|
||
);
|
||
} else if (oldTableAccess == TableAccess.none) {
|
||
resp = await ApiController.to.apiClient.createItem(
|
||
tableName: "table_access",
|
||
itemDefinition: {
|
||
"user_group_id": group.id,
|
||
"table_name": table.tableName,
|
||
"access_type": newTableAccess.def,
|
||
},
|
||
);
|
||
} else {
|
||
resp = await ApiController.to.apiClient.updateItemInTable(
|
||
tableName: "table_access",
|
||
itemUpdate: ItemUpdate(
|
||
item: {
|
||
"access_type": newTableAccess.def,
|
||
},
|
||
oldItem: {
|
||
"user_group_id": group.id,
|
||
"table_name": table.tableName,
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
final respData = resp.data;
|
||
if (respData == null) {
|
||
throw Exception("В ответе нет данных");
|
||
}
|
||
|
||
refreshTableAccess(table);
|
||
} on DioError catch (e) {
|
||
final respData = e.response?.data;
|
||
if (respData != null) {
|
||
Get.snackbar(
|
||
"Ошибка обновления информации доступа к таблицам",
|
||
"${respData['error']}",
|
||
);
|
||
} else {
|
||
Get.snackbar(
|
||
"Ошибка обновления информации доступа к таблицам",
|
||
"$e",
|
||
);
|
||
}
|
||
} catch (e) {
|
||
Get.snackbar(
|
||
"Ошибка обновления информации доступа к таблицам",
|
||
"$e",
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> changeAllowedColumns(TableDefinition table) async {
|
||
final tableColumns = table.columns
|
||
.split(",")
|
||
.map((e) => e.split(":").first)
|
||
.where((e) => e.isNotEmpty)
|
||
.toList();
|
||
final currentlyAvailableColumns =
|
||
_allowedColumns[table.tableId]!.split(",");
|
||
|
||
if (currentlyAvailableColumns.length == 1 &&
|
||
currentlyAvailableColumns.first == "*") {
|
||
currentlyAvailableColumns.clear();
|
||
currentlyAvailableColumns.addAll(tableColumns);
|
||
}
|
||
|
||
final selectedColumns = <String, bool>{}.obs;
|
||
for (final column in tableColumns) {
|
||
selectedColumns[column] = currentlyAvailableColumns.contains(column);
|
||
}
|
||
|
||
final confirm = await Get.dialog<bool>(
|
||
AlertDialog(
|
||
title: const Text("Разрешённые колонки"),
|
||
content: Obx(
|
||
() => Wrap(
|
||
children: [
|
||
ElevatedButton(
|
||
onPressed: () {
|
||
for (final column in tableColumns) {
|
||
selectedColumns[column] = !selectedColumns[column]!;
|
||
}
|
||
},
|
||
child: const Text("Перевернуть"),
|
||
),
|
||
...selectedColumns.entries.map((e) {
|
||
return CheckboxListTile(
|
||
title: Text(e.key),
|
||
value: e.value,
|
||
onChanged: (value) => selectedColumns[e.key] = value!,
|
||
);
|
||
}),
|
||
],
|
||
),
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Get.back(result: false),
|
||
child: const Text("Отменить"),
|
||
),
|
||
TextButton(
|
||
onPressed: () => Get.back(result: true),
|
||
child: const Text("Ок"),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
if (confirm != true) return;
|
||
|
||
if (selectedColumns.values.every((e) => !e)) {
|
||
await Get.dialog(
|
||
AlertDialog(
|
||
title: const Text("Ошибка"),
|
||
content: const Text("Необходимо выбрать хотя бы одну колонку"),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Get.back(),
|
||
child: const Text("Ок"),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
Response<OkResponse> resp =
|
||
await ApiController.to.apiClient.updateItemInTable(
|
||
tableName: "table_access",
|
||
itemUpdate: ItemUpdate(
|
||
item: {
|
||
"allowed_columns": selectedColumns.keys
|
||
.where((k) => selectedColumns[k]!)
|
||
.join(","),
|
||
},
|
||
oldItem: {
|
||
"user_group_id": group.id,
|
||
"table_name": table.tableName,
|
||
},
|
||
),
|
||
);
|
||
|
||
final respData = resp.data;
|
||
if (respData == null) {
|
||
throw Exception("В ответе нет данных");
|
||
}
|
||
|
||
refreshTableAccess(table);
|
||
} on DioError catch (e) {
|
||
final respData = e.response?.data;
|
||
if (respData != null) {
|
||
Get.snackbar(
|
||
"Ошибка обновления информации доступа к таблицам",
|
||
"${respData['error']}",
|
||
);
|
||
} else {
|
||
Get.snackbar(
|
||
"Ошибка обновления информации доступа к таблицам",
|
||
"$e",
|
||
);
|
||
}
|
||
} catch (e) {
|
||
Get.snackbar(
|
||
"Ошибка обновления информации доступа к таблицам",
|
||
"$e",
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
class GroupACLDialog extends GetView<GroupACLController> {
|
||
const GroupACLDialog({Key? key}) : super(key: key);
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return AlertDialog(
|
||
title: const Text('Доступ группы'),
|
||
content: Obx(
|
||
() => DataTable2(
|
||
columns: const [
|
||
DataColumn2(label: Text('Таблица'), size: ColumnSize.L),
|
||
DataColumn2(label: Text('Чтение'), size: ColumnSize.S),
|
||
DataColumn2(label: Text('Запись'), size: ColumnSize.S),
|
||
DataColumn2(label: Text('Колонки'), size: ColumnSize.S),
|
||
],
|
||
empty: const Text("Нет таблиц"),
|
||
rows: controller.access.entries.map((e) {
|
||
final table = controller.tables.firstWhere(
|
||
(element) => element.tableId == e.key,
|
||
);
|
||
final tableAccess = e.value;
|
||
final read = tableAccess == TableAccess.read ||
|
||
tableAccess == TableAccess.readWrite;
|
||
final write = tableAccess == TableAccess.write ||
|
||
tableAccess == TableAccess.readWrite;
|
||
return DataRow(cells: [
|
||
DataCell(Text(table.tableName.pascalCase)),
|
||
DataCell(Checkbox(
|
||
value: read,
|
||
onChanged: (value) => controller.updateTableAccess(
|
||
table,
|
||
read: value ?? false,
|
||
write: write,
|
||
),
|
||
)),
|
||
DataCell(Checkbox(
|
||
value: write,
|
||
onChanged: (value) => controller.updateTableAccess(
|
||
table,
|
||
read: read,
|
||
write: value ?? false,
|
||
),
|
||
)),
|
||
controller.allowedColumns[table.tableId] == null
|
||
? DataCell.empty
|
||
: DataCell(
|
||
Text(controller.allowedColumns[table.tableId]!),
|
||
onTap: () => controller.changeAllowedColumns(table),
|
||
),
|
||
]);
|
||
}).toList(growable: false),
|
||
).constrained(width: Get.width * 0.9, height: Get.height * 0.9),
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () {
|
||
Get.back();
|
||
},
|
||
child: const Text("Закрыть"),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
static Future<void> show(GroupDefinition group) async {
|
||
Get.lazyPut<GroupACLController>(() => GroupACLController(group));
|
||
|
||
await Get.dialog(
|
||
const GroupACLDialog(),
|
||
barrierDismissible: false,
|
||
);
|
||
|
||
Get.delete<GroupACLController>();
|
||
}
|
||
}
|