UI debugging + git ignore

This commit is contained in:
mr 2024-08-22 15:46:16 +02:00
parent ceeebfc964
commit 1db9ef0794
26 changed files with 1568 additions and 302 deletions

44
.gitignore vendored Normal file
View File

@ -0,0 +1,44 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@ -9,6 +9,8 @@ ARG WORKSPACE_HOST="https://localhost:8089"
ARG WORKFLOW_HOST="https://localhost:8088"
ARG SEARCH_HOST="https://localhost:49618"
ARG ITEM_HOST="https://localhost:8087"
ARG SCHEDULER_HOST="http://localhost:8090"
ARG LOGS_HOST="http://localhost:3100"
# define variables
ARG FLUTTER_SDK=/usr/local/flutter

View File

@ -34,6 +34,7 @@ class WorkspaceLocal {
_service.put(context, ws.id!, { "active" : true }, {});
}
if (ws.active == true && changeCurrent) {
print(ws.serialize());
current = ws.id;
}
fill();
@ -95,7 +96,13 @@ class WorkspaceLocal {
});
}
static void changeWorkspaceByName(String name) {
var id = workspaces.entries.firstWhere((element) => element.value.name == "${name}_workspace").key;
changeWorkspace(id);
}
static void changeWorkspace(String id) {
_service.put(null, id, { "active" : true }, {});
current = id;
fill();
endDrawerKey.currentState?.setState(() {});

View File

@ -61,7 +61,7 @@ class SearchWidgetState extends State<SearchWidget> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: MediaQuery.of(context).size.width - 400 > 0 ? MediaQuery.of(context).size.width - 300 - 100 : MediaQuery.of(context).size.width,
width: MediaQuery.of(context).size.width - 400 > 0 ? MediaQuery.of(context).size.width - 300 - 100 : 200,
height: 50,
color: Colors.white,
child: TextField(

View File

@ -74,15 +74,10 @@ class APIService<T extends SerializerDeserializer> {
Future<APIResponse<T>> _main(String url, dynamic body, String method, String succeed, bool force,
BuildContext? context, Options? options) async {
var err = "";
if ((!force) && cache.containsKey(url) && cache[url] != null ) {
return cache[url]! as APIResponse<T>;
}
try {
_dio.options.headers["authorization"] = auth;
_dio.interceptors.clear();
var response = await _request(url, method, body, options);
print(response);
if (response.statusCode != null && response.statusCode! < 400) {
if (method == "delete") { cache.remove(url); return APIResponse<T>(); }
APIResponse<T> resp = APIResponse<T>().deserialize(response.data);
@ -117,6 +112,7 @@ class APIService<T extends SerializerDeserializer> {
try {
_dio.options.headers["authorization"] = auth;
_dio.interceptors.clear();
var response = await _request(url, method, body, null);
if (response.statusCode != null && response.statusCode! < 400) {
if (method == "delete") { cache.remove(url); return APIResponse<RawData>(); }

View File

@ -0,0 +1,36 @@
import 'package:flutter/widgets.dart';
import 'package:oc_front/core/services/api_service.dart';
import 'package:oc_front/core/services/specialized_services/abstract_service.dart';
import 'package:oc_front/models/response.dart';
import 'package:oc_front/models/workflow.dart';
class CheckService extends AbstractService<Check> {
@override APIService<Check> service = APIService<Check>(
baseURL: const String.fromEnvironment('WORKFLOW_HOST', defaultValue: 'http://localhost:8088')
);
@override String subPath = "/oc/workflow/check/";
Future<APIResponse<Check>> search(BuildContext? context, List<String> words, Map<String, dynamic> params) {
return service.get("$subPath${words.join("/")}", true, context);
}
@override
Future<APIResponse<RawData>> all(BuildContext? context) {
throw UnimplementedError();
}
@override
Future<APIResponse<Check>> get(BuildContext? context, String id) {
throw UnimplementedError();
}
@override
Future<APIResponse<Check>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params) {
throw UnimplementedError();
}
@override
Future<APIResponse<Check>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) {
throw UnimplementedError();
}
@override
Future<APIResponse<Check>> delete(BuildContext? context, String id, Map<String, String> params) {
throw UnimplementedError();
}
}

View File

@ -1,11 +0,0 @@
import 'package:oc_front/core/services/api_service.dart';
import 'package:oc_front/core/services/specialized_services/abstract_service.dart';
import 'package:oc_front/models/abstract.dart';
import 'package:oc_front/models/search.dart';
class ItemService<S extends AbstractItem, T extends SerializerDeserializer<S>> extends AbstractService<T> {
@override APIService<T> service = APIService<T>(
baseURL: const String.fromEnvironment('ITEM_HOST', defaultValue: 'http://localhost:8087')
);
@override String subPath = "/oc/${getTopic(S)}/";
}

View File

@ -4,31 +4,31 @@ import 'package:oc_front/core/services/specialized_services/abstract_service.dar
import 'package:oc_front/models/logs.dart';
import 'package:oc_front/models/response.dart';
class LogsService extends AbstractService<LogResults> {
@override APIService<LogResults> service = APIService<LogResults>(
baseURL: const String.fromEnvironment('SCHEDULER_HOST', defaultValue: 'http://localhost:3100')
class LogsService extends AbstractService<LogsResult> {
@override APIService<LogsResult> service = APIService<LogsResult>(
baseURL: const String.fromEnvironment('LOGS_HOST', defaultValue: 'http://localhost:3100')
);
@override String subPath = "/loki/api/v1/";
@override Future<APIResponse<LogResults>> search(BuildContext? context, List<String> words, Map<String, dynamic> params) {
@override Future<APIResponse<LogsResult>> search(BuildContext? context, List<String> words, Map<String, dynamic> params) {
List<String> v = [];
for (var p in params.keys) {
if (p == "start" || p == "end") { continue; }
v.add("$p=\"${params[p]}\"");
}
return service.get("${subPath}query_range?query={${v.join(", ")}}&start=${params["start"]}&end=${params["end"]}", false, context);
return service.get("${subPath}query_range?query={${v.join(", ")}}&start=${params["start"].toString().substring(0, 10)}&end=${params["end"].toString().substring(0, 10)}", false, context);
}
@override Future<APIResponse<LogResults>> get(BuildContext? context, String id) {
@override Future<APIResponse<LogsResult>> get(BuildContext? context, String id) {
throw UnimplementedError();
}
@override Future<APIResponse<LogResults>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params) {
@override Future<APIResponse<LogsResult>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params) {
throw UnimplementedError();
}
@override Future<APIResponse<LogResults>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) {
@override Future<APIResponse<LogsResult>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) {
throw UnimplementedError();
}
@override Future<APIResponse<LogResults>> delete(BuildContext? context, String id, Map<String, String> params) {
@override Future<APIResponse<LogsResult>> delete(BuildContext? context, String id, Map<String, String> params) {
throw UnimplementedError();
}
}

View File

@ -1,32 +1,6 @@
import 'package:oc_front/models/abstract.dart';
import 'package:json_string/json_string.dart';
class LogResults extends SerializerDeserializer<LogResults> {
String? status;
LogsResult? data;
LogResults({
this.status,
this.data,
});
String getID() {
return "";
}
@override deserialize(dynamic json) {
try { json = json as Map<String, dynamic>;
} catch (e) { return LogResults(); }
return LogResults(
status: json.containsKey("status") ? json["status"] : "",
data: json.containsKey("data") ? LogsResult().deserialize(json["data"]) : null,
);
}
@override Map<String, dynamic> serialize() {
return { };
}
}
class LogsResult extends SerializerDeserializer<LogsResult> {
List<Logs> result;
LogsResult({
@ -45,7 +19,9 @@ class LogsResult extends SerializerDeserializer<LogsResult> {
);
}
@override Map<String, dynamic> serialize() {
return { };
return {
"result": toListJson(result),
};
}
}
@ -54,6 +30,7 @@ class Logs extends SerializerDeserializer<Logs> {
List<Log> logs = [];
Logs({
this.level,
this.logs = const [],
});
String getID() {
@ -65,11 +42,13 @@ class Logs extends SerializerDeserializer<Logs> {
} catch (e) { return Logs(); }
return Logs(
level: json.containsKey("stream") && (json["stream"] as Map<String, dynamic>).containsKey("level") ? json["stream"]["level"] : "",
logs: json.containsKey("values") ? fromListJson(json["values"], Log()) : [],
);
}
@override Map<String, dynamic> serialize() {
return { };
return {
"level": level,
};
}
}
@ -78,10 +57,12 @@ class Log extends SerializerDeserializer<Log> {
String? message;
String? level;
String? rawMessage;
Map<String, dynamic> map = {};
Log({
this.timestamp,
this.message,
this.rawMessage,
this.level
});
@ -97,7 +78,7 @@ class Log extends SerializerDeserializer<Log> {
if (j["Status"] == "Pending") {
jsonString = "${j["Name"]} : [${j["Namespace"]}] Status: ${j["Status"]}... \nCreated at ${j["Created"].toString().replaceAllMapped(RegExp(r'\(\w+\)'), (match) { return ''; }).replaceAllMapped(RegExp(r'\+\w+'), (match) { return ''; })}";
} else {
jsonString = "${j["Name"]} : [${j["Namespace"]}] ${j["Status"]} ${j["Progress"]} (${j["Duration"].toString().replaceAll("seconds", "s")})\nStarted at ${j["Created"].toString().replaceAllMapped(RegExp(r'\(\w+\)'), (match) { return ''; }).replaceAllMapped(RegExp(r'\+\w+'), (match) { return ''; })}";
jsonString = "${j["Name"]} : [${j["Namespace"]}] ${j["Status"]} ${j["Progress"]} (${j["Duration"].toString()})\nCreated at ${j["Created"].toString().replaceAllMapped(RegExp(r'\(\w+\)'), (match) { return ''; }).replaceAllMapped(RegExp(r'\+\w+'), (match) { return ''; })}; Started at ${j["Created"].toString().replaceAllMapped(RegExp(r'\(\w+\)'), (match) { return ''; }).replaceAllMapped(RegExp(r'\+\w+'), (match) { return ''; })}";
}
} on JsonFormatException catch (e) { /* */ }
message = jsonString;
@ -107,10 +88,13 @@ class Log extends SerializerDeserializer<Log> {
@override deserialize(dynamic json) {
try { json = json as List<dynamic>;
} catch (e) { return Log(); }
return Log(
timestamp: json.isNotEmpty ? DateTime.parse(json[0]) : null,
var l = Log(
timestamp: json.isNotEmpty ? DateTime.fromMillisecondsSinceEpoch(int.parse(json[0]) ~/ 1000) : null,
message: json.length > 1 ? getMessage(json[1].toString()) : null,
rawMessage : json.length > 1 ? json[1].toString() : null,
);
l.getMessage(l.message ?? "");
return l;
}
@override Map<String, dynamic> serialize() { return { }; }
}

View File

@ -15,7 +15,8 @@ Map<Type, SerializerDeserializer> refs = <Type, SerializerDeserializer> {
Workflow: Workflow(),
Resource: Resource(),
WorkflowExecutions: WorkflowExecutions(),
LogResults: LogResults(),
LogsResult: LogsResult(),
Check: Check(),
};
class APIResponse<T extends SerializerDeserializer> {

View File

@ -1,3 +1,6 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
import 'package:oc_front/models/abstract.dart';
@ -248,10 +251,21 @@ class ProcessingItem extends SerializerDeserializer<ProcessingItem> implements A
@override String getName() {
return name ?? "";
}
double? width;
double? height;
@override
double? getWidth() {
return width;
}
@override
double? getHeight() {
return height;
}
@override deserialize(dynamic json) {
try { json = json as Map<String, dynamic>;
} catch (e) { return ProcessingItem(); }
return ProcessingItem(
var w = ProcessingItem(
id: json.containsKey("id") ? json["id"] : null,
name: json.containsKey("name") ? json["name"] : null,
logo: json.containsKey("logo") ? json["logo"] : null,
@ -274,6 +288,19 @@ class ProcessingItem extends SerializerDeserializer<ProcessingItem> implements A
scallingModel: json.containsKey("scalling_model") ? json["scalling_model"] : null,
diskIO: json.containsKey("disk_io") ? json["disk_io"] : null,
);
if (w.logo != null) {
//
var image = Image.network(w.logo!);
image.image
.resolve(const ImageConfiguration())
.addListener(
ImageStreamListener(
(ImageInfo info, bool _) {
w.width = info.image.width.toDouble();
w.height = info.image.height.toDouble();
}));
}
return w;
}
@override Map<String, dynamic> serialize() => {
"id": id,
@ -336,13 +363,24 @@ class WorkflowItem extends SerializerDeserializer<WorkflowItem> implements Abstr
return id ?? "";
}
double? width;
double? height;
@override
double? getWidth() {
return width;
}
@override
double? getHeight() {
return height;
}
@override String getName() {
return name ?? "";
}
@override deserialize(dynamic json) {
try { json = json as Map<String, dynamic>;
} catch (e) { return WorkflowItem(); }
return WorkflowItem(
var w = WorkflowItem(
id: json.containsKey("id") ? json["id"] : null,
name: json.containsKey("name") ? json["name"] : null,
logo: json.containsKey("logo") ? json["logo"] : null,
@ -358,6 +396,19 @@ class WorkflowItem extends SerializerDeserializer<WorkflowItem> implements Abstr
model: json.containsKey("resource_model") ? ResourceModel().deserialize(json["resource_model"]) : null,
workflowID: json.containsKey("workflow_id") ? json["workflow_id"] : null,
);
if (w.logo != null) {
//
var image = Image.network(w.logo!);
image.image
.resolve(const ImageConfiguration())
.addListener(
ImageStreamListener(
(ImageInfo info, bool _) {
w.width = info.image.width.toDouble();
w.height = info.image.height.toDouble();
}));
}
return w;
}
@override Map<String, dynamic> serialize() => {
"id": id,
@ -420,10 +471,21 @@ class DataItem extends SerializerDeserializer<DataItem> implements AbstractItem<
@override String getID() {
return id ?? "";
}
double? width;
double? height;
@override
double? getWidth() {
return width;
}
@override
double? getHeight() {
return height;
}
@override deserialize(dynamic json) {
try { json = json as Map<String, dynamic>;
} catch (e) { return DataItem(); }
return DataItem(
var w = DataItem(
id: json.containsKey("id") ? json["id"] : null,
name: json.containsKey("name") ? json["name"] : null,
logo: json.containsKey("logo") ? json["logo"] : null,
@ -441,6 +503,19 @@ class DataItem extends SerializerDeserializer<DataItem> implements AbstractItem<
dataType: json.containsKey("data_type") ? json["data_type"] : null,
exemple: json.containsKey("exemple") ? json["exemple"] : null,
);
if (w.logo != null) {
//
var image = Image.network(w.logo!);
image.image
.resolve(const ImageConfiguration())
.addListener(
ImageStreamListener(
(ImageInfo info, bool _) {
w.width = info.image.width.toDouble();
w.height = info.image.height.toDouble();
}));
}
return w;
}
@override Map<String, dynamic> serialize() => {
"id": id,
@ -505,10 +580,21 @@ class DataCenterItem extends SerializerDeserializer<DataCenterItem> implements A
@override String getName() {
return name ?? "";
}
double? width;
double? height;
@override
double? getWidth() {
return width;
}
@override
double? getHeight() {
return height;
}
@override deserialize(dynamic json) {
try { json = json as Map<String, dynamic>;
} catch (e) { return DataCenterItem(); }
return DataCenterItem(
var w = DataCenterItem(
id: json.containsKey("id") ? json["id"] : null,
name: json.containsKey("name") ? json["name"] : null,
logo: json.containsKey("logo") ? json["logo"] : null,
@ -526,6 +612,19 @@ class DataCenterItem extends SerializerDeserializer<DataCenterItem> implements A
gpus: json.containsKey("gpus") ? fromListJson(json["gpus"], GPU()) : [],
ram: json.containsKey("ram") ? RAM().deserialize(json["ram"]) : null,
);
if (w.logo != null) {
//
var image = Image.network(w.logo!);
image.image
.resolve(const ImageConfiguration())
.addListener(
ImageStreamListener(
(ImageInfo info, bool _) {
w.width = info.image.width.toDouble();
w.height = info.image.height.toDouble();
}));
}
return w;
}
@override Map<String, dynamic> serialize() => {
"id": id,
@ -680,10 +779,20 @@ class StorageItem extends SerializerDeserializer<StorageItem> implements Abstrac
@override String getID() {
return id ?? "";
}
double? width;
double? height;
@override
double? getWidth() {
return width;
}
@override
double? getHeight() {
return height;
}
@override deserialize(dynamic json) {
try { json = json as Map<String, dynamic>;
} catch (e) { return StorageItem(); }
return StorageItem(
var w = StorageItem(
id: json.containsKey("id") ? json["id"] : null,
name: json.containsKey("name") ? json["name"] : null,
logo: json.containsKey("logo") ? json["logo"] : null,
@ -705,6 +814,19 @@ class StorageItem extends SerializerDeserializer<StorageItem> implements Abstrac
redundancy: json.containsKey("redundancy") ? json["redundancy"] : null,
throughput: json.containsKey("throughput") ? json["throughput"] : null,
);
if (w.logo != null) {
//
var image = Image.network(w.logo!);
image.image
.resolve(const ImageConfiguration())
.addListener(
ImageStreamListener(
(ImageInfo info, bool _) {
w.width = info.image.width.toDouble();
w.height = info.image.height.toDouble();
}));
}
return w;
}
@override Map<String, dynamic> serialize() => {
"id": id,

View File

@ -3,8 +3,31 @@ import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
import 'package:oc_front/core/models/workspace_local.dart';
import 'package:oc_front/models/abstract.dart';
import 'package:oc_front/models/logs.dart';
import 'package:oc_front/models/search.dart';
class Check extends SerializerDeserializer<Check> {
bool is_available = false;
Check({
this.is_available = false,
});
@override deserialize(dynamic json) {
try { json = json as Map<String, dynamic>;
} catch (e) { return Check(); }
return Check(
is_available: json.containsKey("is_available") ? json["is_available"] : false,
);
}
@override Map<String, dynamic> serialize() {
return {
"is_available": is_available,
};
}
}
class WorkflowExecutions extends SerializerDeserializer<WorkflowExecutions> {
List<WorkflowExecution> executions = [];
String? executionData;
@ -36,6 +59,8 @@ class WorkflowExecution extends SerializerDeserializer<WorkflowExecution> {
String? endDate;
int? status;
String? workflowId;
List<Log>? logs;
WorkflowExecution({
@ -83,6 +108,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
List<dynamic> workflows;
Graph? graph;
Scheduler? schedule;
bool scheduleActive = false;
Workflow({
this.id,
@ -94,6 +120,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
this.workflows = const [],
this.graph,
this.schedule,
this.scheduleActive = false,
});
String getID() {
@ -110,6 +137,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
processing: json.containsKey("processings") ? json["processings"] : [],
datacenter: json.containsKey("datacenters") ? json["datacenters"] : [],
data: json.containsKey("datas") ? json["datas"] : [],
scheduleActive: json.containsKey("schedule_active") ? json["schedule_active"] : false,
storage: json.containsKey("storages") ? json["storages"] : [],
graph: json.containsKey("graph") ? Graph().deserialize(json["graph"]) : null,
schedule: json.containsKey("schedule") ? Scheduler().deserialize(json["schedule"]) : null,
@ -124,6 +152,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
"storages": storage,
"processings": processing,
"workflows": workflows,
"schedule_active": scheduleActive,
"schedule": schedule?.serialize(),
};
if (graph != null) {
@ -135,6 +164,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
void fromDashboard(Map<String, dynamic> j) {
id = j["id"];
name = j["name"];
scheduleActive = j["schedule_active"];
if (j.containsKey("graph")) {
graph = Graph();
graph!.fromDashboard(j["graph"]);
@ -149,6 +179,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
"id": id,
"name": name,
"graph": graph?.toDashboard(),
"schedule_active": scheduleActive,
"schedule": schedule?.toDashboard(),
};
}
@ -160,13 +191,15 @@ class Scheduler extends SerializerDeserializer<Scheduler> {
String? cron;
DateTime? start;
DateTime? end;
int? mode;
Scheduler({
this.id,
this.name,
this.cron,
this.start,
this.end
this.end,
this.mode,
});
void fromDashboard(Map<String, dynamic> j) {
@ -177,13 +210,14 @@ class Scheduler extends SerializerDeserializer<Scheduler> {
if (j.containsKey("end") && j["end"] != null) {
end = DateTime.parse(j["end"]);
}
mode = int.parse(j["mode"].toString());
}
Map<String, dynamic> toDashboard() {
return {
"id": id,
"name": name,
"cron": cron,
"mode": int.parse(mode.toString()),
"start": start?.toIso8601String(),
"end": end?.toIso8601String(),
};
@ -196,6 +230,7 @@ class Scheduler extends SerializerDeserializer<Scheduler> {
id: json.containsKey("id") ? json["id"] : null,
name: json.containsKey("name") ? json["name"] : "",
cron: json.containsKey("cron") ? json["cron"] : "",
mode: json.containsKey("mode") ? json["mode"] : "",
start: json.containsKey("start") ? DateTime.parse(json["start"]) : null,
end: json.containsKey("end") ? DateTime.parse(json["end"]) : null,
);
@ -204,6 +239,7 @@ class Scheduler extends SerializerDeserializer<Scheduler> {
"id": id,
"name": name,
"cron": cron ?? "",
"mode": int.parse(mode.toString()),
"start": start?.toIso8601String(),
"end": end?.toIso8601String(),
};

View File

@ -2,6 +2,7 @@ import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl;
import 'package:go_router/go_router.dart';
import 'package:oc_front/core/services/specialized_services/logs_service.dart';
import 'package:oc_front/core/services/specialized_services/workflow_execution_service.dart';
import 'package:oc_front/models/workflow.dart';
import 'package:oc_front/pages/abstract_page.dart';
@ -35,6 +36,7 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
"${widget.end.year}-${widget.end.month > 9 ? widget.end.month : "0${widget.end.month}"}-${widget.end.day > 9 ? widget.end.day : "0${widget.end.day}"}"], {}),
builder: (ctx, as) {
Map<String, List<WorkflowExecution>> data = {};
if (as.hasData && as.data!.data != null) {
for (var element in as.data!.data!.executions) {
if (element.executionData == null) { continue; }
@ -47,6 +49,49 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
}
}
GlobalKey<ScheduleWidgetState> k = GlobalKey<ScheduleWidgetState>();
for (var da in data.keys) {
for (var exec in data[da]!) {
String start = "";
String end = "";
try {
if (exec.endDate != null && exec.endDate != "") {
var startD = DateTime.parse(exec.executionData!);
var endD = DateTime.parse(exec.endDate!);
var diff = endD.difference(startD);
if (diff.inDays < 30) {
var rest = ((30 - diff.inDays) ~/ 2) - 1;
start = (startD.subtract(Duration(days: rest)).microsecondsSinceEpoch).toString();
end = (endD.add(Duration(days: rest)).microsecondsSinceEpoch).toString();
} else {
start = (startD.microsecondsSinceEpoch).toString();
end = (startD.add( const Duration(days: 29)).microsecondsSinceEpoch).toString();
}
} else {
start = (DateTime.parse(exec.executionData!).subtract( const Duration(days: 14)).microsecondsSinceEpoch).toString();
end = (DateTime.parse(exec.executionData!).add( const Duration(days: 14)).microsecondsSinceEpoch).toString();
}
} catch(e) { /* */ }
k.currentState?.setState(() { k.currentState?.widget.loading = true; });
LogsService().search(context, [], {
"workflow_execution_id": exec.id,
"start": start,
"end": end
}).then((value) {
if (value.data != null) {
var d = value.data!;
for( var r in d.result) {
for (var element in r.logs) {
element.level = r.level;
exec.logs ??= [];
exec.logs!.add(element);
}
exec.logs?.sort((a, b) => a.timestamp!.compareTo(b.timestamp!));
}
}
k.currentState?.setState(() { k.currentState?.widget.loading = false; });
});
}
}
return Column( children: [
Container( color: const Color.fromRGBO(38, 166, 154, 1),
height: 50, width: MediaQuery.of(context).size.width,
@ -69,7 +114,7 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
validator: (value) {
return null;
},
resetIcon: const Icon(Icons.close, size: 15),
resetIcon: null,
onShowPicker: (context, currentValue) async {
var date = await showDatePicker(
builder: (BuildContext context, Widget? child) {
@ -94,6 +139,7 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
);
return date;
},
format: intl.DateFormat('y-M-dd hh:mm:ss'),
initialValue: widget.start,
onChanged: (value) {
@ -126,7 +172,7 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
validator: (value) {
return null;
},
resetIcon: const Icon(Icons.close, size: 15),
resetIcon: null,
onShowPicker: (context, currentValue) async {
var date = await showDatePicker(
builder: (BuildContext context, Widget? child) {
@ -176,7 +222,7 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
)
]))
),
ScheduleWidget( key: k, data: data, start: widget.start, end : widget.end, isList: widget.isList )
ScheduleWidget( key: k, data: data, start: widget.start, end : widget.end, isList: widget.isList, )
]);
});
}

View File

@ -22,6 +22,7 @@ class WorkflowFactory implements AbstractFactory {
@override Widget factory(GoRouterState state, List<String> args) { return WorkflowPageWidget(); }
@override void search(BuildContext context) { }
}
bool getAll = true;
class WorkflowPageWidget extends StatefulWidget {
WorkflowPageWidget () : super(key: WorkflowFactory.key);
@ -107,7 +108,6 @@ final WorflowService _service = WorflowService();
item.position?.x = (item.position?.x ?? 0) + 52.5;
item.position?.y = (item.position?.y ?? 0) + 52.5;
}
print(dash.getZoomFactor());
updateW.graph?.zoom = dash.getZoomFactor();
await _service.put(context, id, updateW.serialize(), {});
}
@ -128,7 +128,9 @@ final WorflowService _service = WorflowService();
Widget menuExtension() {
var quart = MediaQuery.of(context).size.width / 6;
return MenuWorkspaceWidget(simpliest: true, width: quart > 80 ? quart : 80,
onWorkspaceChange: () { dash.selectedLeftMenuKey.currentState?.setState(() { }); });
onWorkspaceChange: () {
dash.selectedLeftMenuKey.currentState?.setState(() { });
});
}
Widget onDashboardAlertOpened(BuildContext context, Dashboard dash) {
@ -148,7 +150,7 @@ final WorflowService _service = WorflowService();
dashboard: dash,
itemWidget: itemBuild,
categories: const ["processing", "data", "datacenter", "storage", "workflows"],
draggableItemBuilder: (cat) => WorkspaceLocal.byTopic(cat, true).toList(),
draggableItemBuilder: (cat) => WorkspaceLocal.byTopic(cat, false),
itemWidgetTooltip: itemTooltipBuild,
innerMenuWidth: quart > 80 ? quart : 80,
menuExtension: menuExtension,

View File

@ -113,11 +113,11 @@ class NewBoxWidgetState<T extends SerializerDeserializer<dynamic>> extends State
? MouseCursor.defer : SystemMouseCursors.click,
onTap: () async {
if (widget._selected == null || widget._selected!.isEmpty) { return; }
if (widget._selected != null && widget.dash.load != null) {
await widget.dash.load!(widget._selected ?? "");
WorkspaceLocal.init(context, true);
}
widget.dash.isOpened = true;
if (widget._selected != null && widget.dash.load != null) {
WorkspaceLocal.changeWorkspaceByName(widget._selected!.split("~")[1]);
await widget.dash.load!(widget._selected ?? "");
}
widget.dash.notifyListeners();
Navigator.pop(context);
},

View File

@ -1,20 +1,46 @@
import 'package:alert_banner/exports.dart';
import 'package:cron/cron.dart';
import 'package:intl/intl.dart' as intl;
import 'package:flutter/material.dart';
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
import 'package:flutter_advanced_switch/flutter_advanced_switch.dart';
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
import 'package:oc_front/core/services/specialized_services/check_service.dart';
import 'package:oc_front/pages/workflow.dart';
import 'package:oc_front/widgets/dialog/alert.dart';
class SchedulerFormsWidget extends StatefulWidget {
Dashboard item;
String purpose = "";
bool? booking;
Function validate = () {};
SchedulerFormsWidget ({ super.key, required this.item });
SchedulerFormsWidget ({ super.key, required this.item, });
@override SchedulerFormsWidgetState createState() => SchedulerFormsWidgetState();
}
class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
CheckService check = CheckService();
@override Widget build(BuildContext context) {
if (widget.item.schedulerState["service"] == null) { widget.item.schedulerState["service"] = true; }
try {
if (widget.item.scheduler["mode"] == null) { widget.item.scheduler["mode"] = 1; }
} catch (e) {
widget.item.scheduler = { "mode": 1 };
}
DateTime? start;
DateTime? end;
if (widget.item.scheduler["start"] != null) {
start = DateTime.parse(widget.item.scheduler["start"]!);
if (start.isBefore(DateTime.now()) && !dash.scheduleActive) {
start = DateTime.now().add(const Duration(minutes: 5));
widget.item.scheduler["start"] = start.toUtc().toIso8601String();
}
}
if (widget.item.scheduler["end"] != null) {
end = DateTime.parse(widget.item.scheduler["end"]!);
if (end.isBefore(DateTime.now()) && !dash.scheduleActive) {
end = DateTime.now().add(const Duration(minutes: 5));
widget.item.scheduler["end"] = end.toUtc().toIso8601String();
}
}
List<GlobalKey<FormFieldState>> formKeys = [GlobalKey<FormFieldState>(), GlobalKey<FormFieldState>(),
GlobalKey<FormFieldState>(), GlobalKey<FormFieldState>()];
return Column( children: [
@ -27,7 +53,7 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
Container(height: 20),
AdvancedSwitch(
width: 140,
initialValue: widget.item.schedulerState["service"] == true,
initialValue: widget.item.scheduler["mode"] == 1,
activeColor: Colors.green, inactiveColor: Colors.green,
activeChild: const Text("service", style: TextStyle(color: Colors.white)),
inactiveChild: const Text("cron task", style: TextStyle(color: Colors.white)),
@ -35,8 +61,8 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 100), () =>
setState(() {
widget.item.schedulerState["service"] = value;
if ((widget.item.schedulerState["service"] == true )) { widget.item.scheduler.remove("cron"); }
widget.item.scheduler["mode"] = value == true ? 1 : 0;
if ((widget.item.scheduler["mode"] == 1 )) { widget.item.scheduler.remove("cron"); }
}));
},),
Container(height: 5),
@ -44,12 +70,18 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
child: Container( height: 40, margin: const EdgeInsets.only(top: 5),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
child: TextFormField( key: formKeys[0],
initialValue: "${widget.item.schedulerState["service"] == true ? "" : "cron_"}${widget.item.scheduler["name"] ?? "${widget.item.name}_event"}",
initialValue: "${widget.item.scheduler["mode"] == 1 ? "" : "cron_"}${widget.item.scheduler["name"] ?? "${widget.item.name}_event"}",
enabled: !dash.scheduleActive,
onChanged: (value) {
Future.delayed(const Duration(seconds: 100), () {
if (widget.item.scheduler["name"] == value) {
widget.item.save!(widget.item.id);
}
});
widget.item.scheduler["name"] = value;
},
onSaved: (value) {
widget.item.scheduler["name"] = value ?? "${widget.item.schedulerState["service"] == true ? "" : "cron_"}${widget.item.scheduler["name"] ?? "${widget.item.name}_event"}";
widget.item.scheduler["name"] = value ?? "${widget.item.scheduler["mode"] == 1 ? "" : "cron_"}${widget.item.scheduler["name"] ?? "${widget.item.name}_event"}";
},
validator: (value) => value == null || value.isEmpty ? "not empty" : null,
style: const TextStyle(fontSize: 12),
@ -72,10 +104,8 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
child: Container( height: 40, margin: const EdgeInsets.only(top: 5),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
child: DateTimeField( key: formKeys[1],
resetIcon: const Icon(Icons.close, size: 15),
onSaved: (value) {
widget.item.scheduler["start"] = "${(value ?? DateTime.now()).toIso8601String()}Z";
},
enabled: !dash.scheduleActive,
resetIcon: null,
onShowPicker: (context, currentValue) async {
var date = await showDatePicker(
builder: (BuildContext context, Widget? child) {
@ -94,38 +124,53 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
return w;
},
context: context,
firstDate: DateTime(1900),
initialDate: DateTime.parse(widget.item.scheduler["start"] ?? currentValue?.toIso8601String() ?? ""),
firstDate: dash.scheduleActive ? DateTime(1900) : DateTime.now().add(const Duration(minutes: 5)),
initialDate: DateTime.parse( start?.toLocal().toIso8601String()
?? currentValue?.toIso8601String()
?? DateTime.now().add(const Duration(minutes: 5)).toUtc().toIso8601String()).toLocal(),
lastDate: DateTime(2100)
);
if (date != null) {
var time = await showTimePicker(context: context,
initialTime: TimeOfDay(hour: date.hour, minute: date.minute),
builder: (BuildContext context, Widget? child) {
Widget w = Theme(
data: ThemeData(
cardTheme: CardTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
dialogTheme: DialogTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
colorScheme: ColorScheme.light(
background: Colors.grey.shade300,
tertiary: Colors.grey,
secondary: Colors.grey,
primary: Colors.black),
),
child: child ?? Container(),
var n = TimeOfDay.now();
TimeOfDay? time = n;
var count = 0;
while(((time?.hour ?? 0) + ((time?.minute ?? 0) / 100)) <= (n.hour + ((n.minute + 1) / 100)) ) {
if (count > 0 && time != null) {
showAlertBanner( context, () {},
const AlertAlertBannerChild(
text: "must be at least 1 minute from now to let system check info"),// <-- Put any widget here you want!
alertBannerLocation: AlertBannerLocation.bottom,);
}
time = await showTimePicker(context: context,
initialTime: TimeOfDay(hour: date.hour, minute: date.minute),
builder: (BuildContext context, Widget? child) {
Widget w = Theme(
data: ThemeData(
cardTheme: CardTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
dialogTheme: DialogTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
colorScheme: ColorScheme.light(
background: Colors.grey.shade300,
tertiary: Colors.grey,
secondary: Colors.grey,
primary: Colors.black),
),
child: child ?? Container(),
);
return w;
},
);
return w;
},
);
date = date.add(Duration(hours: time?.hour ?? 0, minutes: time?.minute ?? 0));
if (time == null) { return DateTime.now().add( const Duration(minutes: 1)); }
count++;
}
date = date.add(Duration(hours: time?.hour ?? 0, minutes: time?.minute ?? 0));
widget.item.scheduler["start"] = date.toUtc().toIso8601String();
widget.item.save!(widget.item.id);
}
return date;
},
format: intl.DateFormat('y-M-dd hh:mm:ss'),
initialValue: DateTime.parse(widget.item.scheduler["start"] ?? DateTime.now().toIso8601String()),
onChanged: (value) {
widget.item.scheduler["start"] = "${(value ?? DateTime.now()).toIso8601String()}Z";
},
format: intl.DateFormat('y-M-dd HH:mm:ss'),
initialValue: start?.toLocal() ?? DateTime.now(),
onChanged: (value) { },
validator: (value) => value == null ? "not empty" : null,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
@ -147,8 +192,14 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
child: Container( height: 40, margin: const EdgeInsets.only(top: 5),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
child: DateTimeField( key: formKeys[2],
enabled: !dash.scheduleActive,
validator: (value) {
return value == null && !(widget.item.schedulerState["service"] == true ) ? "not empty" : null;
return value == null && !(widget.item.scheduler["mode"] == 1 ) ? "not empty" : null;
},
onChanged: (value) {
if (value == null) {
widget.item.scheduler.remove("end");
}
},
resetIcon: const Icon(Icons.close, size: 15),
onShowPicker: (context, currentValue) async {
@ -169,45 +220,52 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
return w;
},
context: context,
firstDate: DateTime(1900),
initialDate: DateTime.parse(widget.item.scheduler["start"] ?? currentValue?.toIso8601String() ?? ""),
firstDate: dash.scheduleActive ? DateTime(1900) : DateTime.now().add(const Duration(minutes: 5)),
initialDate: DateTime.parse( end?.toLocal().toIso8601String()
?? currentValue?.toIso8601String()
?? DateTime.now().add(const Duration(minutes: 5)).toUtc().toIso8601String()).toLocal(),
lastDate: DateTime(2100)
);
if (date != null) {
var time = await showTimePicker(context: context,
initialTime: TimeOfDay(hour: date.hour, minute: date.minute),
builder: (BuildContext context, Widget? child) {
Widget w = Theme(
data: ThemeData(
cardTheme: CardTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
dialogTheme: DialogTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
colorScheme: ColorScheme.light(
background: Colors.grey.shade300,
tertiary: Colors.grey,
secondary: Colors.grey,
primary: Colors.black),
),
child: child ?? Container(),
// ignore: use_build_context_synchronously
var n = TimeOfDay.now();
TimeOfDay? time = TimeOfDay(hour: date.hour, minute: date.minute);
var count = 0;
while(((time?.hour ?? 0) + (time?.minute ?? 0 / 100)) <= (n.hour + ((n.minute + 1) / 100)) ) {
if (count > 0 && time != null) {
showAlertBanner( context, () {},
const AlertAlertBannerChild(text: "must be at least 1 minute from now to let system check info"),// <-- Put any widget here you want!
alertBannerLocation: AlertBannerLocation.bottom,);
}
time = await showTimePicker(context: context,
initialTime: TimeOfDay(hour: date.hour, minute: date.minute),
builder: (BuildContext context, Widget? child) {
Widget w = Theme(
data: ThemeData(
cardTheme: CardTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
dialogTheme: DialogTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
colorScheme: ColorScheme.light(
background: Colors.grey.shade300,
tertiary: Colors.grey,
secondary: Colors.grey,
primary: Colors.black),
),
child: child ?? Container(),
);
return w;
},
);
return w;
},
);
date = date.add(Duration(hours: time?.hour ?? 0, minutes: time?.minute ?? 0));
if (time == null) { return null; }
count++;
}
date = date.add(Duration(hours: time?.hour ?? 0, minutes: time?.minute ?? 0));
widget.item.scheduler["end"] = date.toUtc().toIso8601String();
widget.item.save!(widget.item.id);
}
return date;
},
format: intl.DateFormat('y-M-dd hh:mm:ss'),
initialValue: widget.item.scheduler["end"] != null ? DateTime.parse(widget.item.scheduler["end"]!) : null,
onSaved: (value) {
if (value != null) {
widget.item.scheduler["end"] = "${(value).toIso8601String()}Z";
}
},
onChanged: (value) {
if (value == null) { return; }
widget.item.scheduler["end"] = "${value.toIso8601String()}Z";
},
format: intl.DateFormat('y-M-dd HH:mm:ss'),
initialValue: end?.toLocal(),
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
fillColor: Colors.white,
@ -215,7 +273,7 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
filled: true,
alignLabelWithHint: false,
hintText: "enter end event...",
labelText: "end event${!(widget.item.schedulerState["service"] == true) ? "*" : ""}",
labelText: "end event${!(widget.item.scheduler["mode"] == 1) ? "*" : ""}",
errorStyle: const TextStyle(fontSize: 0),
hintStyle: const TextStyle(fontSize: 10),
labelStyle: const TextStyle(fontSize: 10),
@ -224,12 +282,18 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
),
))),
widget.item.schedulerState["service"] == true ? Container() : Tooltip( message: "schedule",
widget.item.scheduler["mode"] == 1 ? Container() : Tooltip( message: "schedule",
child: Container( height: 40, margin: const EdgeInsets.only(top: 5),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
child: TextFormField( key: formKeys[3],
enabled: !dash.scheduleActive,
initialValue: widget.item.scheduler["cron"],
onChanged: (value) {
Future.delayed(const Duration(seconds: 100), () {
if (widget.item.scheduler["cron"] == value) {
widget.item.save!(widget.item.id);
}
});
widget.item.scheduler["cron"] = value;
},
onSaved: (value) {
@ -263,9 +327,48 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
),
))),
const Divider(color: Colors.grey),
Tooltip( message: "save",
Tooltip( message: "check booking",
child: InkWell( mouseCursor: SystemMouseCursors.click,
onTap: () {
if (dash.scheduler["start"] == null ) {
DateTime now = DateTime.now().add(const Duration(minutes: 5));
dash.scheduler["start"] = now.toUtc().toIso8601String();
}
var s = DateTime.parse(dash.scheduler["start"]).toUtc().toIso8601String();
var e = "";
if (dash.scheduler["end"] == null) {
e = DateTime.parse(dash.scheduler["start"]).add(const Duration(seconds: 5)).toUtc().toIso8601String();
} else {
e = DateTime.parse(dash.scheduler["end"]).toUtc().toIso8601String();
}
check.search(context, [widget.item.id ?? "", s.substring(0, 19), e.substring(0, 19)], {}).then(
(v) {
if (v.data == null) { return; }
widget.booking = v.data!.is_available;
if (v.data!.is_available) {
showAlertBanner( context, () {},
const InfoAlertBannerChild(text: "no booking found at this date"),// <-- Put any widget here you want!
alertBannerLocation: AlertBannerLocation.bottom,);
} else {
showAlertBanner( context, () {},
const AlertAlertBannerChild(text: "booking found at this date"),// <-- Put any widget here you want!
alertBannerLocation: AlertBannerLocation.bottom,);
}
setState(() {});
}
);
}, child: Container( margin: const EdgeInsets.all(10),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5),
border: Border.all(color: widget.booking == null ? Colors.black : (widget.booking == true ? Colors.green : Colors.red), width: 1)),
width: 140, height: 30,
child: Icon(
Icons.verified_outlined, color:widget.booking == null ? Colors.black : (widget.booking == true? Colors.green : Colors.red)),
))
),
Tooltip( message: dash.scheduleActive ? "unbook" : "book",
child: InkWell( mouseCursor: SystemMouseCursors.click,
onTap: () {
dash.scheduleActive = !dash.scheduleActive;
for (var k in formKeys) {
if (k.currentState != null) {
if (!k.currentState!.validate()) {
@ -273,12 +376,18 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
} else { k.currentState!.save();}
}
}
widget.item.schedulerSave = true;
if (dash.scheduler["start"] == null ) {
DateTime now = DateTime.now().add(const Duration(minutes: 5));
dash.scheduler["start"] = now.toUtc().toIso8601String();
}
widget.item.save!(widget.item.id);
setState(() { });
}, child: Container( margin: const EdgeInsets.all(10),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), border: Border.all(color: Colors.black, width: 1)),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5),
border: Border.all(color: dash.scheduleActive ? Colors.green : Colors.black, width: 1)),
width: 140, height: 30,
child: const Icon(Icons.save_outlined, color: Colors.black),
child: Icon(
dash.scheduleActive ? Icons.cancel_schedule_send : Icons.schedule_send, color: dash.scheduleActive ? Colors.green : Colors.black),
))
),
]);

View File

@ -32,7 +32,8 @@ class ItemRowWidgetState extends State<ItemRowWidget> {
height: 100,
decoration: BoxDecoration( border: Border(bottom: BorderSide(color: Colors.grey.shade300)) ),
child: Row( children: [
widget.low ? Container( padding: const EdgeInsets.only(left: 10),) : Padding( padding: const EdgeInsets.all(10),
widget.low ? Container( padding: const EdgeInsets.only(left: 10),) : Container( padding: const EdgeInsets.all(10),
constraints: BoxConstraints(maxWidth: imageSize, minWidth: imageSize),
child: image ?? Image.network('https://get-picto.com/wp-content/uploads/2024/01/logo-instagram-png.webp',
height: imageSize, width: imageSize)),
Container(

View File

@ -0,0 +1,824 @@
/*
BSD 3-Clause License
Copyright (c) 2023, Birju Vachhani
*/
import 'dart:ui';
import 'package:box_transform/box_transform.dart';
import 'package:flutter/material.dart';
import 'package:flutter_box_transform/flutter_box_transform.dart';
/// A widget that allows you to resize and drag a box around a widget.
class TransformableBox extends StatefulWidget {
/// If you need more control over the [TransformableBox] you can pass a
/// custom [TransformableBoxController] instance through the [controller]
/// parameter.
///
/// If you do not specify one, a default [TransformableBoxController] instance
/// will be created internally, along with its lifecycle.
final TransformableBoxController? controller;
/// A builder function that is used to build the content of the
/// [TransformableBox]. This is the physical widget you wish to show resizable
/// handles on. It's most commonly something like an image widget, but it
/// could be anything you want to have resizable & draggable box handles on.
final TransformableChildBuilder contentBuilder;
/// A builder function that is used to build the corners handles of the
/// [TransformableBox]. If you don't specify it, the default handles will be
/// used.
///
/// Note that this will build for all four corners of the rectangle.
final HandleBuilder cornerHandleBuilder;
/// A builder function that is used to build the side handles of the
/// [TransformableBox]. If you don't specify it, the default handles will be
/// used.
///
/// Note that this will build for all four sides of the rectangle.
final HandleBuilder sideHandleBuilder;
/// The size of the gesture response area of the handles. If you don't
/// specify it, the default value will be used.
///
/// This is similar to Flutter's [MaterialTapTargetSize] property, in which
/// the actual handle size is smaller than the gesture response area. This is
/// done to improve accessibility and usability of the handles; users will not
/// need cursor precision over the handle's pixels to be able to perform
/// operations with them, they need only to be able to reach the handle's
/// gesture response area to make it forgiving.
///
/// The default value is 24 pixels in diameter.
final double handleTapSize;
/// A set containing handles that are enabled. This is different from
/// [visibleHandles].
///
/// [enabledHandles] determines which handles are
/// interactive and can be used to resize the box. [visibleHandles]
/// determines which handles are visible. If a handle is visible but not
/// enabled, it will not be interactive. If a handle is enabled but not
/// visible, it will not be shown and will not be interactive.
final Set<HandlePosition> enabledHandles;
/// A set containing which handles to show. This is different from
/// [enabledHandles].
///
/// [enabledHandles] determines which handles are
/// interactive and can be used to resize the box. [visibleHandles]
/// determines which handles are visible. If a handle is visible but not
/// enabled, it will not be interactive. If a handle is enabled but not
/// visible, it will not be shown and will not be interactive.
final Set<HandlePosition> visibleHandles;
/// The initial box that will be used to position set the initial size of
/// the [TransformableBox] widget.
///
/// This initial box will be mutated by the [TransformableBoxController] through
/// different dragging, panning, and resizing operations.
///
/// [Rect] is immutable, so a new [Rect] instance will be created every time
/// the [TransformableBoxController] mutates the box. You can acquire your
/// updated box through the [onChanged] callback or through an externally
/// provided [TransformableBoxController] instance.
final Rect rect;
/// The initial flip that will be used to set the initial flip of the
/// [TransformableBox] widget. Normally, flipping is done by the user through
/// the handles, but you can set the initial flip through this parameter in
/// case the initial state of the box is in a flipped state.
///
/// This utility cannot predicate if a box is flipped or not, so you will
/// need to provide the correct initial flip state.
///
/// Note that the flip is optional, if you're resizing an image, for example,
/// you might want to allow flipping of the image when the user drags the
/// handles to opposite corners of the box. This flip behavior is entirely
/// optional and will allow handling such cases.
///
/// You can leave it at the default [Flip.none] if flipping is not desired.
/// Note that this will not prevent the drag handles from crossing to
/// opposite corners of the box, it will only give oyu a lack of information
/// on the state of the box if flipping were to occur.
final Flip flip;
/// A box that will contain the [rect] inside of itself, forcing [rect] to
/// be clamped inside of this [clampingRect].
final Rect clampingRect;
/// A set of constraints that will be applied to the [rect] when it is
/// resized by the [TransformableBoxController].
final BoxConstraints constraints;
/// Whether the box is resizable or not. Setting this to false will disable
/// all resizing operations. This is a convenience parameter that will ignore
/// the [enabledHandles] parameter and set all handles to disabled.
final bool resizable;
/// Whether the box is movable or not. Setting this to false will disable
/// all moving operations.
final bool draggable;
/// Whether to allow flipping of the box while resizing. If this is set to
/// true, the box will flip when the user drags the handles to opposite
/// corners of the rect.
final bool allowFlippingWhileResizing;
/// Decides whether to flip the contents of the box when the box is flipped.
/// If this is set to true, the contents will be flipped when the box is
/// flipped.
final bool allowContentFlipping;
/// How to align the handles.
final HandleAlignment handleAlignment;
/// The callback function that is used to resolve the [ResizeMode] based on
/// the pressed keys on the keyboard.
final ValueGetter<ResizeMode> resizeModeResolver;
/// A callback that is called every time the [TransformableBox] is updated.
/// This is called every time the [TransformableBoxController] mutates the box
/// or the flip.
final RectChangeEvent? onChanged;
/// A callback that is called when [TransformableBox] triggers a pointer down
/// event to begin a drag operation.
final RectDragStartEvent? onDragStart;
/// A callback that is called every time the [TransformableBox] is moved.
/// This is called every time the [TransformableBoxController] mutates the
/// box through a drag operation.
///
/// This is different from [onChanged] in that it is only called when the
/// box is moved, not when the box is resized.
final RectDragUpdateEvent? onDragUpdate;
/// A callback that is called every time the [TransformableBox] is completes
/// its drag operation via the pan end event.
final RectDragEndEvent? onDragEnd;
/// A callback that is called every time the [TransformableBox] cancels
/// its drag operation via the pan cancel event.
final RectDragCancelEvent? onDragCancel;
/// A callback function that triggers when the box is about to start resizing.
final RectResizeStart? onResizeStart;
/// A callback that is called every time the [TransformableBox] is resized.
/// This is called every time the [TransformableBoxController] mutates the
/// box.
///
/// This is different from [onChanged] in that it is only called when the box
/// is resized, not when the box is moved.
final RectResizeUpdateEvent? onResizeUpdate;
/// A callback function that triggers when the box is about to end resizing.
final RectResizeEnd? onResizeEnd;
/// A callback function that triggers when the box cancels resizing.
final RectResizeCancel? onResizeCancel;
/// A callback function that triggers when the box reaches its minimum width
/// when resizing.
final TerminalEdgeEvent? onMinWidthReached;
/// A callback function that triggers when the box reaches its maximum width
/// when resizing.
final TerminalEdgeEvent? onMaxWidthReached;
/// A callback function that triggers when the box reaches its minimum height
/// when resizing.
final TerminalEdgeEvent? onMinHeightReached;
/// A callback function that triggers when the box reaches its maximum height
/// when resizing.
final TerminalEdgeEvent? onMaxHeightReached;
/// A callback function that triggers when the box reaches a terminal width
/// when resizing. A terminal width is a width that is either the minimum or
/// maximum width of the box.
///
/// This function combines both [onMinWidthReached] and [onMaxWidthReached]
/// into one callback function.
final TerminalAxisEvent? onTerminalWidthReached;
/// A callback function that triggers when the box reaches a terminal height
/// when resizing. A terminal height is a height that is either the minimum or
/// maximum height of the box.
///
/// This function combines both [onMinHeightReached] and [onMaxHeightReached]
/// into one callback function.
final TerminalAxisEvent? onTerminalHeightReached;
/// A callback function that triggers when the box reaches a terminal size
/// when resizing. A terminal size is a size that is either the minimum or
/// maximum size of the box on either axis.
///
/// This function combines both [onTerminalWidthReached] and
/// [onTerminalHeightReached] into one callback function.
final TerminalEvent? onTerminalSizeReached;
/// Whether to paint the handle's bounds for debugging purposes.
final bool debugPaintHandleBounds;
final double handleTapLeftSize;
/// Creates a [TransformableBox] widget.
const TransformableBox({
super.key,
required this.contentBuilder,
this.controller,
this.cornerHandleBuilder = _defaultCornerHandleBuilder,
this.sideHandleBuilder = _defaultSideHandleBuilder,
this.handleTapSize = 24,
this.handleTapLeftSize = 24,
this.allowContentFlipping = true,
this.handleAlignment = HandleAlignment.center,
this.enabledHandles = const {...HandlePosition.values},
this.visibleHandles = const {...HandlePosition.values},
// Raw values.
Rect? rect,
Flip? flip,
Rect? clampingRect,
BoxConstraints? constraints,
ValueGetter<ResizeMode>? resizeModeResolver,
// Additional controls.
this.resizable = true,
this.draggable = true,
this.allowFlippingWhileResizing = true,
// Either resize or drag triggers.
this.onChanged,
// Resize events
this.onResizeStart,
this.onResizeUpdate,
this.onResizeEnd,
this.onResizeCancel,
// Drag Events.
this.onDragStart,
this.onDragUpdate,
this.onDragEnd,
this.onDragCancel,
// Terminal update events.
this.onMinWidthReached,
this.onMaxWidthReached,
this.onMinHeightReached,
this.onMaxHeightReached,
this.onTerminalWidthReached,
this.onTerminalHeightReached,
this.onTerminalSizeReached,
this.debugPaintHandleBounds = false,
}) : assert(
(controller == null) ||
((rect == null) &&
(flip == null) &&
(clampingRect == null) &&
(constraints == null) &&
(resizeModeResolver == null)),
'If a controller is provided, the raw values should not be provided.',
),
rect = rect ?? Rect.zero,
flip = flip ?? Flip.none,
clampingRect = clampingRect ?? Rect.largest,
constraints = constraints ?? const BoxConstraints.expand(),
resizeModeResolver = resizeModeResolver ?? defaultResizeModeResolver;
/// Returns the [TransformableBox] of the closest ancestor.
static TransformableBox? widgetOf(BuildContext context) {
return context.findAncestorWidgetOfExactType<TransformableBox>();
}
/// Returns the [TransformableBoxController] of the closest ancestor.
static TransformableBoxController? controllerOf(BuildContext context) {
return context
.findAncestorStateOfType<_TransformableBoxState>()
?.controller;
}
@override
State<TransformableBox> createState() => _TransformableBoxState();
}
class _TransformableBoxState extends State<TransformableBox> {
late TransformableBoxController controller;
bool isLegalGesture = false;
@override
void initState() {
super.initState();
if (widget.controller != null) {
controller = widget.controller!;
// We only want to listen to the controller if it is provided externally.
controller.addListener(onControllerUpdate);
} else {
// If it is provided internally, we should not listen to it.
controller = TransformableBoxController(
rect: widget.rect,
flip: widget.flip,
clampingRect: widget.clampingRect,
constraints: widget.constraints,
resizeModeResolver: widget.resizeModeResolver,
allowFlippingWhileResizing: widget.allowFlippingWhileResizing,
);
}
}
@override
void didUpdateWidget(covariant TransformableBox oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != null && oldWidget.controller == null ||
widget.controller != oldWidget.controller) {
// New explicit controller provided or explicit controller changed.
controller.removeListener(onControllerUpdate);
controller = widget.controller!;
controller.addListener(onControllerUpdate);
} else if (oldWidget.controller != null && widget.controller == null) {
// Explicit controller removed.
controller.removeListener(onControllerUpdate);
controller = TransformableBoxController(
rect: widget.rect,
flip: widget.flip,
clampingRect: widget.clampingRect,
constraints: widget.constraints,
resizeModeResolver: widget.resizeModeResolver,
allowFlippingWhileResizing: widget.allowFlippingWhileResizing,
);
}
// Return if the controller is external.
if (widget.controller != null) return;
// Below code should only be executed if the controller is internal.
bool shouldRecalculatePosition = false;
bool shouldRecalculateSize = false;
if (oldWidget.rect != widget.rect) {
controller.setRect(widget.rect, notify: false);
}
if (oldWidget.flip != widget.flip) {
controller.setFlip(widget.flip, notify: false);
}
if (oldWidget.resizeModeResolver != widget.resizeModeResolver) {
controller.setResizeModeResolver(
widget.resizeModeResolver,
notify: false,
);
}
if (oldWidget.clampingRect != widget.clampingRect) {
controller.setClampingRect(widget.clampingRect, notify: false);
shouldRecalculatePosition = true;
}
if (oldWidget.constraints != widget.constraints) {
controller.setConstraints(widget.constraints, notify: false);
shouldRecalculateSize = true;
}
if (oldWidget.allowFlippingWhileResizing !=
widget.allowFlippingWhileResizing) {
controller.setAllowFlippingWhileResizing(
widget.allowFlippingWhileResizing,
notify: false,
);
}
if (shouldRecalculatePosition) {
controller.recalculatePosition(notify: false);
}
if (shouldRecalculateSize) {
controller.recalculateSize(notify: false);
}
}
@override
void dispose() {
controller.removeListener(onControllerUpdate);
if (widget.controller == null) controller.dispose();
super.dispose();
}
/// Called when the controller is updated.
void onControllerUpdate() {
if (widget.rect != controller.rect || widget.flip != controller.flip) {
if (mounted) setState(() {});
}
}
/// Called when the handle drag starts.
void onHandlePanStart(DragStartDetails event, HandlePosition handle) {
// Two fingers were used to start the drag. This produces issues with
// the box drag event. Therefore, we ignore it.
if (event.kind == PointerDeviceKind.trackpad) {
isLegalGesture = false;
return;
} else {
isLegalGesture = true;
}
controller.onResizeStart(event.localPosition);
widget.onResizeStart?.call(handle, event);
}
/// Called when the handle drag updates.
void onHandlePanUpdate(DragUpdateDetails event, HandlePosition handle) {
if (!isLegalGesture) return;
final UIResizeResult result = controller.onResizeUpdate(
event.localPosition,
handle,
);
widget.onChanged?.call(result, event);
widget.onResizeUpdate?.call(result, event);
widget.onMinWidthReached?.call(result.minWidthReached);
widget.onMaxWidthReached?.call(result.maxWidthReached);
widget.onMinHeightReached?.call(result.minHeightReached);
widget.onMaxHeightReached?.call(result.maxHeightReached);
widget.onTerminalWidthReached?.call(
result.minWidthReached,
result.maxWidthReached,
);
widget.onTerminalHeightReached?.call(
result.minHeightReached,
result.maxHeightReached,
);
widget.onTerminalSizeReached?.call(
result.minWidthReached,
result.maxWidthReached,
result.minHeightReached,
result.maxHeightReached,
);
}
/// Called when the handle drag ends.
void onHandlePanEnd(DragEndDetails event, HandlePosition handle) {
if (!isLegalGesture) return;
controller.onResizeEnd();
widget.onResizeEnd?.call(handle, event);
widget.onMinWidthReached?.call(false);
widget.onMaxWidthReached?.call(false);
widget.onMinHeightReached?.call(false);
widget.onMaxHeightReached?.call(false);
widget.onTerminalWidthReached?.call(false, false);
widget.onTerminalHeightReached?.call(false, false);
widget.onTerminalSizeReached?.call(false, false, false, false);
}
void onHandlePanCancel(HandlePosition handle) {
if (!isLegalGesture) return;
controller.onResizeEnd();
widget.onResizeCancel?.call(handle);
widget.onMinWidthReached?.call(false);
widget.onMaxWidthReached?.call(false);
widget.onMinHeightReached?.call(false);
widget.onMaxHeightReached?.call(false);
widget.onTerminalWidthReached?.call(false, false);
widget.onTerminalHeightReached?.call(false, false);
widget.onTerminalSizeReached?.call(false, false, false, false);
}
/// Called when the box drag event starts.
void onDragPanStart(DragStartDetails event) {
// Two fingers were used to start the drag. This produces issues with
// the box drag event. Therefore, we ignore it.
if (event.kind == PointerDeviceKind.trackpad) {
isLegalGesture = false;
return;
} else {
isLegalGesture = true;
}
controller.onDragStart(event.localPosition);
widget.onDragStart?.call(event);
}
/// Called when the box drag event updates.
void onDragPanUpdate(DragUpdateDetails event) {
if (!isLegalGesture) return;
final UIMoveResult result = controller.onDragUpdate(
event.localPosition,
);
widget.onChanged?.call(result, event);
widget.onDragUpdate?.call(result, event);
}
/// Called when the box drag event ends.
void onDragPanEnd(DragEndDetails event) {
if (!isLegalGesture) return;
controller.onDragEnd();
widget.onDragEnd?.call(event);
}
void onDragPanCancel() {
if (!isLegalGesture) return;
controller.onDragEnd();
widget.onDragCancel?.call();
}
@override
Widget build(BuildContext context) {
final Flip flip = controller.flip;
final Rect rect = controller.rect;
Widget content = Transform.scale(
scaleX: widget.allowContentFlipping && flip.isHorizontal ? -1 : 1,
scaleY: widget.allowContentFlipping && flip.isVertical ? -1 : 1,
child: widget.contentBuilder(context, rect, flip),
);
if (widget.draggable) {
content = GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: onDragPanStart,
onPanUpdate: onDragPanUpdate,
onPanEnd: onDragPanEnd,
onPanCancel: onDragPanCancel,
child: content,
);
}
return SizedBox(
width: rect.width,
height: rect.height,
child: Stack(
clipBehavior: Clip.none,
fit: StackFit.expand,
children: [
Positioned(
left: widget.handleAlignment.offset(widget.handleTapLeftSize),
top: widget.handleAlignment.offset(widget.handleTapSize),
width: rect.width,
height: rect.height,
child: content,
),
if (widget.resizable)
for (final handle in HandlePosition.corners.where((handle) =>
widget.visibleHandles.contains(handle) ||
widget.enabledHandles.contains(handle)))
CornerHandleWidget(
key: ValueKey(handle),
handlePosition: handle,
handleTapSize: widget.handleTapSize,
enabled: widget.enabledHandles.contains(handle),
visible: widget.visibleHandles.contains(handle),
onPanStart: (event) => onHandlePanStart(event, handle),
onPanUpdate: (event) => onHandlePanUpdate(event, handle),
onPanEnd: (event) => onHandlePanEnd(event, handle),
onPanCancel: () => onHandlePanCancel(handle),
builder: widget.cornerHandleBuilder,
),
if (widget.resizable)
for (final handle in HandlePosition.sides.where((handle) =>
widget.visibleHandles.contains(handle) ||
widget.enabledHandles.contains(handle)))
SideHandleWidget(
key: ValueKey(handle),
handlePosition: handle,
handleTapSize: widget.handleTapSize,
enabled: widget.enabledHandles.contains(handle),
visible: widget.visibleHandles.contains(handle),
onPanStart: (event) => onHandlePanStart(event, handle),
onPanUpdate: (event) => onHandlePanUpdate(event, handle),
onPanEnd: (event) => onHandlePanEnd(event, handle),
onPanCancel: () => onHandlePanCancel(handle),
builder: widget.sideHandleBuilder,
),
],
) ,
);
}
}
/// A default implementation of the corner [HandleBuilder] callback.
Widget _defaultCornerHandleBuilder(
BuildContext context,
HandlePosition handle,
) =>
DefaultCornerHandle(handle: handle);
/// A default implementation of the side [HandleBuilder] callback.
Widget _defaultSideHandleBuilder(
BuildContext context,
HandlePosition handle,
) =>
DefaultSideHandle(handle: handle);
@protected
class CornerHandleWidget extends StatelessWidget {
/// The position of the handle.
final HandlePosition handlePosition;
/// The builder that is used to build the handle widget.
final HandleBuilder builder;
/// The size of the handle's gesture response area.
final double handleTapSize;
/// Called when the handle dragging starts.
final GestureDragStartCallback? onPanStart;
/// Called when the handle dragging is updated.
final GestureDragUpdateCallback? onPanUpdate;
/// Called when the handle dragging ends.
final GestureDragEndCallback? onPanEnd;
/// Called when the handle dragging is canceled.
final GestureDragCancelCallback? onPanCancel;
/// Whether the handle is resizable.
final bool enabled;
/// Whether the handle is visible.
final bool visible;
/// Whether to paint the handle's bounds for debugging purposes.
final bool debugPaintHandleBounds;
/// Creates a new handle widget.
CornerHandleWidget({
super.key,
required this.handlePosition,
required this.handleTapSize,
required this.builder,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onPanCancel,
this.enabled = true,
this.visible = true,
this.debugPaintHandleBounds = false,
}) : assert(handlePosition.isDiagonal, 'A corner handle must be diagonal.');
@override
Widget build(BuildContext context) {
Widget child =
visible ? builder(context, handlePosition) : const SizedBox.shrink();
if (enabled) {
child = GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart: onPanStart,
onPanUpdate: onPanUpdate,
onPanEnd: onPanEnd,
onPanCancel: onPanCancel,
child: MouseRegion(
cursor: getCursorForHandle(handlePosition),
child: child,
),
);
}
return Positioned(
left: handlePosition.influencesLeft ? 0 : null,
right: handlePosition.influencesRight ? 0 : null,
top: handlePosition.influencesTop ? 0 : null,
bottom: handlePosition.influencesBottom ? 0 : null,
width: handleTapSize,
height: handleTapSize,
child: child,
);
}
/// Returns the cursor for the given handle position.
MouseCursor getCursorForHandle(HandlePosition handle) {
switch (handle) {
case HandlePosition.topLeft:
case HandlePosition.bottomRight:
return SystemMouseCursors.resizeUpLeftDownRight;
case HandlePosition.topRight:
case HandlePosition.bottomLeft:
return SystemMouseCursors.resizeUpRightDownLeft;
default:
throw Exception('Invalid handle position.');
}
}
}
/// Creates a new cardinal handle widget, with its appropriate gesture splash
/// zone.
@protected
class SideHandleWidget extends StatelessWidget {
/// The position of the handle.
final HandlePosition handlePosition;
/// The builder that is used to build the handle widget.
final HandleBuilder builder;
/// The thickness of the handle that is used for gesture detection.
final double handleTapSize;
/// Called when the handle dragging starts.
final GestureDragStartCallback? onPanStart;
/// Called when the handle dragging is updated.
final GestureDragUpdateCallback? onPanUpdate;
/// Called when the handle dragging ends.
final GestureDragEndCallback? onPanEnd;
/// Called when the handle dragging is canceled.
final GestureDragCancelCallback? onPanCancel;
/// Whether the handle is resizable.
final bool enabled;
/// Whether the handle is visible.
final bool visible;
/// Whether to paint the handle's bounds for debugging purposes.
final bool debugPaintHandleBounds;
/// Creates a new handle widget.
SideHandleWidget({
super.key,
required this.handlePosition,
required this.handleTapSize,
required this.builder,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onPanCancel,
this.enabled = true,
this.visible = true,
this.debugPaintHandleBounds = false,
}) : assert(handlePosition.isSide, 'A cardinal handle must be cardinal.');
@override
Widget build(BuildContext context) {
Widget child =
visible ? builder(context, handlePosition) : const SizedBox.shrink();
if (enabled) {
child = GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart: onPanStart,
onPanUpdate: onPanUpdate,
onPanEnd: onPanEnd,
onPanCancel: onPanCancel,
child: MouseRegion(
cursor: getCursorForHandle(handlePosition),
child: child,
),
);
}
return Positioned(
left: handlePosition.isVertical
? handleTapSize
: handlePosition.influencesLeft
? 0
: null,
right: handlePosition.isVertical
? handleTapSize
: handlePosition.influencesRight
? 0
: null,
top: handlePosition.isHorizontal
? handleTapSize
: handlePosition.influencesTop
? 0
: null,
bottom: handlePosition.isHorizontal
? handleTapSize
: handlePosition.influencesBottom
? 0
: null,
width: handlePosition.isHorizontal ? handleTapSize : null,
height: handlePosition.isVertical ? handleTapSize : null,
child: child,
);
}
/// Returns the cursor for the given handle position.
MouseCursor getCursorForHandle(HandlePosition handle) {
switch (handle) {
case HandlePosition.left:
case HandlePosition.right:
return SystemMouseCursors.resizeLeftRight;
case HandlePosition.top:
case HandlePosition.bottom:
return SystemMouseCursors.resizeUpDown;
default:
throw Exception('Invalid handle position.');
}
}
}

View File

@ -1,18 +1,28 @@
import 'package:alert_banner/exports.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:json_string/json_string.dart';
import 'package:oc_front/core/sections/header/header.dart';
import 'package:oc_front/models/logs.dart';
import 'package:oc_front/widgets/dialog/alert.dart';
class LogsWidget extends StatefulWidget {
final List<Log> items;
LogsWidget ({ Key? key, required this.items }): super(key: key);
String? level;
String search = "";
LogsWidget ({ Key? key, this.search = "", required this.items, this.level }): super(key: key);
@override LogsWidgetState createState() => LogsWidgetState();
}
class LogsWidgetState extends State<LogsWidget> {
@override Widget build(BuildContext context) {
List<LogWidget> itemRows = widget.items.map((e) => LogWidget(item: e)).toList();
return SingleChildScrollView( child: Column( children: itemRows ) );
List<LogWidget> itemRows = widget.items.where((element) => (element.message?.toLowerCase().contains(widget.search.toLowerCase()) ?? true)
&& (widget.level?.contains(element.level ?? "") ?? true) ).map((e) => LogWidget(item: e)).toList();
return Stack( children: [
SingleChildScrollView( child: itemRows.isEmpty ?
Container( height: MediaQuery.of(context).size.height - 100 - HeaderConstants.height,
child: const Center( child: Text("no log registered", style: TextStyle(color: Colors.grey, fontSize: 25 ),)))
: Column( children: [...itemRows, Container(height: 50,) ] ) ),
]);
}
}
@ -24,23 +34,31 @@ class LogWidget extends StatefulWidget {
}
class LogWidgetState extends State<LogWidget> {
@override Widget build(BuildContext context) {
return Padding( padding: const EdgeInsets.only(top: 10, left: 30, right: 30), child: Wrap( children: [
Map<String, dynamic> map = {};
try { map = JsonString(widget.item.rawMessage?.replaceAll("\\", "") ?? "").decodedValue as Map<String, dynamic>;
} catch (e) { /* */}
return Container(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.white)),
),
padding: const EdgeInsets.only(top: 10, left: 30, right: 30, bottom: 10),
child: Wrap( children: [
Row( mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container( width: 10, height: 15, color: widget.item.level?.toLowerCase() == "info" ? Colors.green :
( widget.item.level?.toLowerCase() == "error" ? Colors.red : (
widget.item.level?.toLowerCase() == "warning" ? Colors.orange : Colors.blue))),
InkWell( mouseCursor: widget.item.map.isEmpty ? MouseCursor.defer : SystemMouseCursors.click, onTap: () {
if (widget.item.map.isNotEmpty ) {
InkWell( mouseCursor: map.isEmpty ? MouseCursor.defer : SystemMouseCursors.click, onTap: () {
if (map.isNotEmpty ) {
setState(() {
widget.expanded = !widget.expanded;
});
}
}, child: Container( height: 20,
}, child: SizedBox( height: 20,
child: Padding( padding: EdgeInsets.symmetric(horizontal: widget.expanded ? 0 : 5),
child: Icon( widget.expanded ? Icons.keyboard_arrow_down_outlined : Icons.arrow_forward_ios, size: widget.expanded ? 25 : 15,
color: widget.item.map.isEmpty ? Colors.grey : Colors.black, weight: widget.expanded ? 100 : 1000,)))),
color: map.isEmpty ? Colors.grey : Colors.black, weight: widget.expanded ? 100 : 1000,)))),
Padding( padding: const EdgeInsets.only(right: 10),
child: Text("${widget.item.timestamp?.toString()}",
style: const TextStyle(fontSize: 13, color: Colors.black, fontWeight: FontWeight.w500))),
@ -57,10 +75,10 @@ class LogWidgetState extends State<LogWidget> {
decoration: BoxDecoration( color: Colors.grey,
borderRadius: BorderRadius.circular(4)),
padding: const EdgeInsets.all(10),
child: Column( children: widget.item.map.keys.map((e) =>
child: Column( children: map.keys.map((e) =>
Padding( padding: const EdgeInsets.all(2), child: Row( mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [Flexible( child:Text("$e: \"${widget.item.map[e]}\"",
children: [Flexible( child:Text("$e: \"${map[e]}\"",
style: const TextStyle(fontSize: 11, color: Colors.white))), ])
)).toList()
)) : Container(),

View File

@ -1,7 +1,3 @@
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:oc_front/core/models/workspace_local.dart';
class MenuWorkspaceWidget extends StatefulWidget {

View File

@ -1,26 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_box_transform/flutter_box_transform.dart';
import 'package:oc_front/core/sections/header/header.dart';
import 'package:oc_front/core/services/specialized_services/logs_service.dart';
import 'package:oc_front/models/logs.dart';
import 'package:oc_front/models/workflow.dart';
import 'package:oc_front/widgets/logs.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:oc_front/widgets/lib/tranformablebox.dart' as fork;
import 'package:oc_front/widgets/sheduler_items/scheduler_calendar.dart';
import 'package:oc_front/widgets/sheduler_items/scheduler_item.dart';
double menuSize = 300;
// ignore: must_be_immutable
class ScheduleWidget extends StatefulWidget {
DateTime start;
DateTime end;
bool isDayPlanner = true;
bool loading = true;
Map<String, List<WorkflowExecution>> data;
bool isList = true;
ScheduleWidget ({ super.key, required this.data, required this.start, required this.end, this.isList = true });
ScheduleWidget ({ super.key, required this.data, required this.start, required this.end, this.isList = true, this.loading = false});
@override ScheduleWidgetState createState() => ScheduleWidgetState();
}
String? selected;
String? selectedReal;
class ScheduleWidgetState extends State<ScheduleWidget> {
LogsService _service = LogsService();
String? selected;
String? selectedReal;
String search = "";
String? level;
List<Color> colors = [Colors.blue, Colors.orange, Colors.red, Colors.green];
List<String> titles = ["SCHEDULED", "RUNNING", "FAILURE", "SUCCESS"];
@ -31,7 +39,7 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
@override Widget build(BuildContext context) {
bool isInfo = MediaQuery.of(context).size.width <= 600 && selected != null;
double w = selected != null ? MediaQuery.of(context).size.width - 300 : MediaQuery.of(context).size.width;
double w = selected != null ? MediaQuery.of(context).size.width - menuSize : MediaQuery.of(context).size.width;
List<Widget> children = [];
if (selected != null) {
for (var wf in widget.data[selected!] ?? (<WorkflowExecution>[])) {
@ -40,7 +48,8 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
onTap: () => setState(() { selectedReal = wf.executionData; }),
child: Container( margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
border: Border.all(color: selectedReal != null && selectedReal == wf.executionData ? const Color.fromRGBO(38, 166, 154, 1) : Colors.transparent, width: 2),
border: Border.all(color: selectedReal != null && selectedReal == wf.executionData ?
const Color.fromRGBO(38, 166, 154, 1) : Colors.transparent, width: 2),
borderRadius: BorderRadius.circular(4), color: Colors.white
),
child: Container(
@ -52,13 +61,13 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
borderRadius: BorderRadius.circular(4),
),
),
Container( width: (400 - 250),
SizedBox( width: (menuSize - 140),
child: Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(wf.name?.toUpperCase() ?? "", overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.black, fontSize: 12, fontWeight: FontWeight.w500)),
)),
Container(
SizedBox(
child: Padding(
padding: const EdgeInsets.only(left: 20),
child: Text("${d2.hour > 9 ? d2.hour : "0${d2.hour}"}:${d2.minute > 9 ? d2.minute : "0${d2.minute}"}", overflow: TextOverflow.ellipsis,
@ -70,48 +79,45 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
}
}
List<Log> logs = [];
String? selectedID;
String? start;
String? end;
if (selectedReal != null) {
try {
var sel = widget.data[selected!]!.firstWhere((element) => element.executionData == selectedReal);
selectedID = sel.id;
print(sel.endDate);
if (sel.endDate != null && sel.endDate != "") {
var startD = DateTime.parse(sel.executionData!);
var endD = DateTime.parse(sel.endDate!);
var diff = endD.difference(startD);
if (diff.inDays < 30) {
var rest = ((30 - diff.inDays) ~/ 2) - 1;
start = (startD.subtract(Duration(days: rest)).microsecondsSinceEpoch).toString();
end = (endD.add(Duration(days: rest)).microsecondsSinceEpoch).toString();
} else {
start = (startD.microsecondsSinceEpoch).toString();
end = (startD.add( const Duration(days: 29)).microsecondsSinceEpoch).toString();
}
} else {
start = (DateTime.parse(sel.executionData!).subtract( const Duration(days: 14)).microsecondsSinceEpoch).toString();
end = (DateTime.parse(sel.executionData!).add( const Duration(days: 14)).microsecondsSinceEpoch).toString();
}
logs = sel.logs ?? [];
} catch(e) { /* */ }
}
menuSize = isInfo ? MediaQuery.of(context).size.width : (menuSize > MediaQuery.of(context).size.width / 2 ? MediaQuery.of(context).size.width / 2 : menuSize);
Rect rect = Rect.fromCenter( center: MediaQuery.of(context).size.center(Offset.zero),
width: selected != null ? menuSize : 0, height: (MediaQuery.of(context).size.height - HeaderConstants.height - 50) > 0 ? (MediaQuery.of(context).size.height - HeaderConstants.height - 50) : 0);
return Row( children: [
isInfo ? Container() : SizedBox( width: w,
child: widget.isList ? SchedulerItemWidget(data: widget.data, parent: this, focusedDay: getFocusedDay(), width: w)
: SchedulerCalendarWidget(data: widget.data, start: widget.start,
end: widget.end, parent: this, focusedDay: getFocusedDay(),)
),
Container(
fork.TransformableBox(
rect: rect, constraints: BoxConstraints(
maxWidth: isInfo ? MediaQuery.of(context).size.width : (selected != null ? MediaQuery.of(context).size.width / 2 : 0),
minWidth: selected != null ? 300 : 0),
handleTapSize: 1, handleTapLeftSize: 0, allowFlippingWhileResizing: false, draggable: false, flip: null,
resizeModeResolver: () => ResizeMode.freeform,
visibleHandles: const {HandlePosition.left},
enabledHandles: const {HandlePosition.left},
clampingRect: Offset.zero & MediaQuery.sizeOf(context),
handleAlignment: HandleAlignment.inside,
onChanged: (result, event) { setState(() { menuSize = result.rect.width; }); },
contentBuilder: (context, rect, flip) { return Container(
height: MediaQuery.of(context).size.height - HeaderConstants.height - 50,
width: isInfo ? MediaQuery.of(context).size.width : (selected != null ? 300 : 0),
width: isInfo ? MediaQuery.of(context).size.width : (selected != null ? menuSize : 0),
color: Colors.grey.shade300,
child: Column(
children: [
Row( children: [
InkWell( onTap: () => setState(() { widget.isDayPlanner = true; }),
child: Tooltip( message: "day planning", child:
Container( height: 50, width: (isInfo ? MediaQuery.of(context).size.width : (selected != null ? 300 : 0)) / (selectedReal != null ? 2 : 1 ),
Container( height: 50, width: (isInfo ? MediaQuery.of(context).size.width : (selected != null ? menuSize : 0)) / (selectedReal != null ? 2 : 1 ),
alignment: Alignment.center,
decoration: BoxDecoration(
color: widget.isDayPlanner ? Colors.grey : Colors.transparent,
@ -121,7 +127,8 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
)),
InkWell( onTap: () => setState(() { widget.isDayPlanner = false; }),
child: Tooltip( message: "monitor task", child:
Container( height: 50, width: selectedReal == null ? 0 : ((isInfo ? MediaQuery.of(context).size.width : (selected != null ? 300 : 0)) / 2),
Container( height: 50, width: selectedReal == null ? 0 : (
(isInfo ? MediaQuery.of(context).size.width : (selected != null ? menuSize : 0)) / 2),
alignment: Alignment.center,
decoration: BoxDecoration(
color: !widget.isDayPlanner ? Colors.grey : Colors.transparent,
@ -132,48 +139,84 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
)
))
]),
Container( width: isInfo ? MediaQuery.of(context).size.width : (selected != null ? 300 : 0), height: MediaQuery.of(context).size.height - HeaderConstants.height - 100,
child: SingleChildScrollView( child: Column(
mainAxisAlignment: children.isEmpty ? MainAxisAlignment.center : MainAxisAlignment.start,
children: [
...( widget.isDayPlanner ? children : ( selectedID != null ? [
FutureBuilder(future: _service.search(context, [], {
"workflow_execution_id": selectedID,
"start": start,
"end": end
}), builder: (ctx, as) {
var speLog = Log(level: "error", timestamp: DateTime.now());
speLog.getMessage("{\"Name\":\"oc-monitor-unonip-fauta9hswg\",\"Namespace\":\"argo\",\"Status\":\"Pending\",\"PodRunning\":false,\"Completed\":false,\"Created\":\"Tue Aug 06 11:33:52 +0200 (now)\",\"Started\":\"\",\"Duration\":\"\",\"Progress\":\"\"}");
var speLog2 = Log(level: "warning", timestamp: DateTime.now());
speLog2.getMessage("{\"Name\":\"oc-monitor-unonip-fauta9hswg\",\"Namespace\":\"argo\",\"Status\":\"Running\",\"PodRunning\":false,\"Completed\":false,\"Created\":\"Tue Aug 06 11:33:52 +0200 (now)\",\"Started\":\"Tue Aug 06 11:33:52 +0200 (now)\",\"Duration\":\"0 seconds\",\"Progress\":\"0/1\"}");
List<Log> logs = [
Log(
level: "info",
message: "No logs found",
timestamp: DateTime.now()
SizedBox( width: isInfo ? MediaQuery.of(context).size.width : (selected != null ? menuSize : 0), height: MediaQuery.of(context).size.height - HeaderConstants.height - (!widget.isDayPlanner && !widget.loading ? 150 : 100 ),
child: Stack( children: [
SingleChildScrollView( child: Column(
mainAxisAlignment: children.isEmpty || widget.loading ? MainAxisAlignment.center : MainAxisAlignment.start,
children: [
...( widget.isDayPlanner ? children : ( selectedID != null ? [
widget.loading ? const SpinKitCircle(color: Colors.white,) : LogsWidget(items: logs, search: search, level: level)
] : [])),
children.isEmpty ? Container( height: 100, alignment: Alignment.center, child: const Text("No event found", style: TextStyle(color: Colors.grey, fontSize: 20))) : Container()
]),
) ])
),
!widget.isDayPlanner && !widget.loading ?
Row( children: [
Container(
width: 150,
height: 50,
decoration: BoxDecoration(
border: Border(left: BorderSide(color: Colors.grey.shade300)),
),
child: DropdownButtonFormField(
isExpanded: true,
value: level,
style: const TextStyle(fontSize: 12),
hint: const Text("by level...", style: TextStyle(fontSize: 12)),
decoration: InputDecoration(
filled: true,
focusedBorder: const OutlineInputBorder( borderRadius: BorderRadius.zero,
borderSide: BorderSide(color: Color.fromARGB(38, 166, 154, 1), width: 0),
),
fillColor: Colors.white,
contentPadding: const EdgeInsets.only(left: 30, right: 30, top: 10, bottom: 30),
enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.zero,
borderSide: BorderSide(color: Colors.grey.shade300, width: 0),
),
border: OutlineInputBorder( borderRadius: BorderRadius.zero,
borderSide: BorderSide(color: Colors.grey.shade300, width: 0)),
),
speLog,
speLog2
];
if (as.hasData && as.data!.data != null) {
var d = as.data!.data!;
for( var r in d.data?.result ?? <Logs> []) {
for (var element in r.logs) {
element.level = r.level;
logs.add(element);
}
}
}
logs.sort((a, b) => a.timestamp!.compareTo(b.timestamp!));
return LogsWidget(items: logs);
})
] : [])),
children.isEmpty ? Container( height: 100, alignment: Alignment.center, child: const Text("No event found", style: const TextStyle(color: Colors.grey, fontSize: 20))) : Container()
]))
)
items: [
DropdownMenuItem(value: "debug,warning,error,info", child: Row( children: [ Container( width: 10, height: 15, color: Colors.grey), Text(" all", style: TextStyle(fontSize: 12, color: Colors.black)) ])),
DropdownMenuItem(value: "debug", child: Row( children: [ Container( width: 10, height: 15, color: Colors.blue), Text(" debug", style: TextStyle(fontSize: 12, color: Colors.black)) ])),
DropdownMenuItem(value: "warning", child: Row( children: [ Container( width: 10, height: 15, color: Colors.orange), Text(" warning", style: TextStyle(fontSize: 12, color: Colors.black)) ])),
DropdownMenuItem(value: "error", child: Row( children: [ Container( width: 10, height: 15, color: Colors.red), Text(" error", style: TextStyle(fontSize: 12, color: Colors.black)) ])),
DropdownMenuItem(value: "info", child: Row( children: [ Container( width: 10, height: 15, color: Colors.green), Text(" info", style: TextStyle(fontSize: 12, color: Colors.black)) ])),
],
onChanged: (value) {
setState(() {
level = value;
});
})),
Container(
width: menuSize - 150,
height: 50,
decoration: BoxDecoration(
border: Border(left: BorderSide(color: Colors.grey.shade300)),
),
child: TextField(
onChanged: (value) { setState(() {
search = value;
});},
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
hintText: "by logs...",
fillColor: Colors.white,
filled: true,
contentPadding: EdgeInsets.symmetric(horizontal: 30),
hintStyle: TextStyle(
color: Colors.black,
fontSize: 12,
fontWeight: FontWeight.w300
),
border: InputBorder.none
)
)) ]
) : Container( ),
],
),
)
); })
]);
}
}

View File

@ -50,13 +50,14 @@ class SchedulerCalendarWidgetState extends State<SchedulerCalendarWidget> {
markerBuilder: (context, day, events) {
List<Widget> children = [];
for (var ev in events) {
if (children.length == 2 && events.length > 3) {
if (children.length == 1 && events.length > 2) {
children.add( InkWell( onTap: () => widget.parent!.setState(() {
widget.parent!.selected = day.toIso8601String();
widget.parent!.selectedReal = null;
selected = day.toIso8601String();
selectedReal = null;
widget.parent!.widget.isDayPlanner = true;
}),
child: Container(
margin: const EdgeInsets.only(bottom: 5),
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
@ -67,13 +68,14 @@ class SchedulerCalendarWidgetState extends State<SchedulerCalendarWidget> {
break;
}
children.add(InkWell( onTap: () => widget.parent!.setState(() {
widget.parent!.selected = day.toIso8601String();
widget.parent!.selectedReal = ev.executionData;
if (widget.parent!.selectedReal == null) {
selected = day.toIso8601String();
selectedReal = ev.executionData;
if (selectedReal == null) {
widget.parent!.widget.isDayPlanner = true;
}
}),
child: Container(
margin: const EdgeInsets.only(bottom: 2.5, top: 25),
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
@ -146,8 +148,8 @@ class SchedulerCalendarWidgetState extends State<SchedulerCalendarWidget> {
}),
onDaySelected: (selectedDay, focusedDay) {
widget.parent!.setState(() {
widget.parent!.selected = selectedDay.toIso8601String();
widget.parent!.selectedReal = null;
selected = selectedDay.toIso8601String();
selectedReal = null;
widget.parent!.widget.isDayPlanner = true;
});
},

View File

@ -27,21 +27,21 @@ class SchedulerItemWidgetState extends State<SchedulerItemWidget> {
widget.keys[ev.executionData!] = GlobalKey();
var d2 = DateTime.parse(ev.executionData!);
DateTime? d3;
try {
d3 = DateTime.parse(ev.endDate!);
try { d3 = DateTime.parse(ev.endDate!);
} catch (e) { /* */ }
widgets.add(InkWell(
onTap: () => widget.parent?.setState(() {
widget.parent?.selected = widget.parent?.selected != element ? element : null;
widget.parent?.selectedReal = widget.parent?.selected == null ? null : ev.executionData;
if (widget.parent!.selectedReal == null) {
selected = selected != element || ev.executionData != selectedReal ? element : null;
selectedReal = selected == null ? null : ev.executionData;
if (selectedReal == null) {
widget.parent!.widget.isDayPlanner = true;
}
}),
child: Container( key: widget.keys[ev.executionData!],
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 50),
decoration: BoxDecoration(
border: widget.parent?.selected == element ? Border.all(color: const Color.fromRGBO(38, 166, 154, 1), width: 2)
border: selectedReal == ev.executionData ?
Border.all(color: const Color.fromRGBO(38, 166, 154, 1), width: 2)
: Border(top: BorderSide(color: Colors.grey.shade300)),
),
child: Row(children: [
@ -79,37 +79,39 @@ class SchedulerItemWidgetState extends State<SchedulerItemWidget> {
)));
}
var date = DateTime.parse(element);
children.add(Column( children: [Container(
child: ExpansionTile(
enabled: widget.enabled,
shape: ContinuousRectangleBorder(),
iconColor: Colors.grey,
initiallyExpanded: true,
title: SizedBox(
child : Row( children: [
const Padding(padding: EdgeInsets.only(right: 10),
child: Icon(Icons.view_day, color: Colors.grey)),
Flexible(
child: Padding(
padding: const EdgeInsets.only(right: 5),
child: Text("${date.day > 9 ? date.day : "0${date.day}"}-${date.hour > 9 ? date.hour : "0${date.hour}"}-${date.year}".toUpperCase(), overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.w500))))
])
),
collapsedIconColor: Colors.grey,
children: widgets,
)),
children.add(Column( children: [ExpansionTile(
enabled: widget.enabled,
shape: const ContinuousRectangleBorder(),
iconColor: Colors.grey,
initiallyExpanded: true,
title: SizedBox(
child : Row( children: [
const Padding(padding: EdgeInsets.only(right: 10),
child: Icon(Icons.view_day, color: Colors.grey)),
Flexible(
child: Padding(
padding: const EdgeInsets.only(right: 5),
child: Text("${date.day > 9 ? date.day : "0${date.day}"}-${date.month > 9 ? date.month : "0${date.month}"}-${date.year}".toUpperCase(), overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.w500))))
])
),
collapsedIconColor: Colors.grey,
children: widgets,
),
Divider(color: Colors.grey.shade300, height: 1)
]));
}
Future.delayed( const Duration(milliseconds: 100), () {
if (widget.parent?.selectedReal != null) {
widget.keys[widget.parent!.selectedReal!]?.currentContext?.findRenderObject()?.showOnScreen();
if (selectedReal != null) {
widget.keys[selectedReal!]?.currentContext?.findRenderObject()?.showOnScreen();
}
});
return SingleChildScrollView( child: Container(
return Container(
alignment: children.isNotEmpty ? Alignment.topLeft : Alignment.center,
color: children.isNotEmpty ? Colors.transparent : Colors.grey.shade300,
width: children.isNotEmpty ? MediaQuery.of(context).size.width : null,
height: MediaQuery.of(context).size.height - HeaderConstants.height - 50,
child: Column( children: children))
child: children.isNotEmpty ? SingleChildScrollView( child: Column( children: children)) : const Text("NO DATA FOUND", style: TextStyle(color: Colors.grey, fontSize: 30))
);
}
}

View File

@ -30,8 +30,7 @@ class Dashboard extends ChangeNotifier {
List<Map<String, dynamic>> tempHistory = [];
List<Map<String, dynamic>> history = [];
Map<String, dynamic> scheduler = {};
Map<String, bool> schedulerState = {};
bool schedulerSave = false;
bool scheduleActive = false;
String? id;
String name;
String defaultName = "";
@ -108,6 +107,7 @@ class Dashboard extends ChangeNotifier {
map['defaultArrowDirection'] as int? ?? 0],
defaultArrowStyle: ArrowStyle.values[map['arrowStyle'] as int? ?? 0],
);
d..scheduleActive = map['schedule_active'] as bool? ?? false;
d..arrows = List<ArrowPainter>.from(
(map['arrows'] as List<dynamic>).map<ArrowPainter>(
(x) => ArrowPainter.fromMap(x as Map<String, dynamic>),
@ -137,6 +137,7 @@ class Dashboard extends ChangeNotifier {
}
void copyFromMap(Map<String, dynamic> map) {
scheduleActive = map['schedule_active'] as bool? ?? false;
scheduler = map['schedule'] as Map<String, String>? ?? {};
defaultArrowStyle = ArrowStyle.values[map['arrowStyle'] as int? ?? 0];
defaultDashSpace = map['defaultDashSpace'] as double? ?? 0;
@ -222,17 +223,16 @@ class Dashboard extends ChangeNotifier {
d["id"]=id;
d["name"]=name;
d["graph"]=graph;
if (schedulerSave) {
d["schedule"]=scheduler;
}
d["schedule"]=scheduler;
d["schedule_active"]=scheduleActive;
return d;
}
void deserialize(Map<String, dynamic> graph) {
elements = [];
arrows = [];
print(graph['schedule']);
scheduler = graph['schedule'] ?? {};
scheduleActive = graph['schedule_active'] ?? false;
setZoomFactor(graph["graph"]?["zoom"] ?? 1.0);
for(var el in graph['graph']?['elements'] ?? []) {
List<ConnectionParams> nexts = [];

View File

@ -4,7 +4,6 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
import 'package:flutter_flow_chart/src/dashboard.dart';
import 'package:uuid/uuid.dart';
/// Kinf od element

View File

@ -19,6 +19,9 @@ import 'package:uuid/uuid.dart';
abstract class FlowData {
String getID();
String getName();
double? getWidth();
double? getHeight();
Map<String, dynamic> serialize();
FlowData deserialize(Map<String, dynamic> data);
}
@ -297,6 +300,20 @@ class FlowChartState<T extends FlowData> extends State<FlowChart> {
@override
Widget build(BuildContext context) {
if (!widget.dashboard.isOpened && widget.onDashboardAlertOpened != null ) {
Future.delayed(Duration(milliseconds: 100), () {
showDialog(
barrierDismissible: false,
context: context, builder: (context) {
return AlertDialog(
titlePadding: EdgeInsets.zero,
insetPadding: EdgeInsets.zero,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(0)),
title: widget.onDashboardAlertOpened!(context, widget.dashboard));
}); });
}
/// get dashboard position after first frame is drawn
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (mounted) {
@ -379,9 +396,6 @@ class FlowChartState<T extends FlowData> extends State<FlowChart> {
if (change) {
DrawingArrow.instance.notifyListeners();
widget.dashboard.chartKey.currentState?.setState(() { });
/*Future.delayed(Duration(milliseconds: 10), () {
node.requestFocus();
});*/
}
},
child: ClipRect(
@ -421,12 +435,16 @@ class FlowChartState<T extends FlowData> extends State<FlowChart> {
onAcceptWithDetails: (DragTargetDetails<T> details) {
var e = details.data;
String newID = const Uuid().v4();
double ratio = 1;
if (e.getWidth() != null && e.getHeight() != null) {
ratio = (e.getHeight()! / (e.getWidth()! - 30));
}
FlowElement<T> el = FlowElement<T>(
dashboard: widget.dashboard,
id: newID,
element: e,
position: details.offset,
size: const Size(100, 100),
size: Size(100, 100 * ratio),
text: '${widget.dashboard.elements.length}',
handlerSize: 15,
widget: widget.itemWidget(e),
@ -477,33 +495,22 @@ class FlowChartState<T extends FlowData> extends State<FlowChart> {
onDragEnd: (d) => node.requestFocus(),
childWhenDragging: Opacity(opacity: .5,
child: Padding( padding: const EdgeInsets.all(10),
child: Container( height: realSize - 20, child: widget.itemWidget(e) ))),
feedback: Container( height: realSize, child: widget.itemWidget(e) ),
child: Container( child: widget.itemWidget(e),
constraints: BoxConstraints(maxHeight: realSize - 20, maxWidth: realSize - 20), ))),
feedback: Container( constraints: BoxConstraints(maxHeight: realSize, maxWidth: realSize),
child: widget.itemWidget(e) ),
child: InkWell( mouseCursor: SystemMouseCursors.grab, child: Padding( padding: const EdgeInsets.all(10),
child: widget.itemWidgetTooltip != null ? HoverMenu( key: hoverKey, width: 400, title: Container(
height: realSize - 20, child: widget.itemWidget(e) ),
constraints: BoxConstraints( maxHeight: realSize - 20, maxWidth: realSize - 20),
child: widget.itemWidget(e) ),
items: [
Container(child: widget.itemWidgetTooltip!(e)),
]
) : Container(
height: realSize - 20, child: widget.itemWidget(e)
constraints: BoxConstraints(maxHeight: realSize - 20, maxWidth: realSize - 20), child: widget.itemWidget(e)
)
) )));
}
if (!widget.dashboard.isOpened && widget.onDashboardAlertOpened != null ) {
Future.delayed(Duration(milliseconds: 100), () {
showDialog(
barrierDismissible: false,
context: context, builder: (context) {
return AlertDialog(
titlePadding: EdgeInsets.zero,
insetPadding: EdgeInsets.zero,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(0)),
title: widget.onDashboardAlertOpened!(context, widget.dashboard));
}); });
}
return res;
}
}