From c747dcd4ae3cffb9dc2ccce434288382d61bc6b1 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Fri, 29 Jul 2022 08:38:21 +0300 Subject: [PATCH] Implement Server Storage card for provider menu Co-authored-by: Inex Code --- analysis_options.yaml | 1 + assets/translations/en.json | 10 + .../schema/get_server_disk_volumes.graphql | 11 + .../get_server_disk_volumes.graphql.dart | 542 ++++++++++++++++++ .../get_server_disk_volumes.graphql.g.dart | 64 +++ .../graphql_maps/schema/schema.graphql | 13 + .../api_maps/graphql_maps/schema/server.dart | 19 + .../server_installation_cubit.dart | 6 + lib/logic/models/json/server_disk_volume.dart | 22 + .../models/json/server_disk_volume.g.dart | 25 + lib/logic/models/state_types.dart | 2 +- .../brand_linear_indicator.dart | 37 ++ .../icon_status_mask/icon_status_mask.dart | 6 + lib/ui/pages/providers/providers.dart | 19 + lib/ui/pages/providers/storage_card.dart | 152 +++++ 15 files changed, 928 insertions(+), 1 deletion(-) create mode 100644 lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql create mode 100644 lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql.dart create mode 100644 lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql.g.dart create mode 100644 lib/logic/models/json/server_disk_volume.dart create mode 100644 lib/logic/models/json/server_disk_volume.g.dart create mode 100644 lib/ui/components/brand_linear_indicator/brand_linear_indicator.dart create mode 100644 lib/ui/pages/providers/storage_card.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 9d16cb20..aa344a69 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -13,6 +13,7 @@ analyzer: exclude: - lib/generated_plugin_registrant.dart - lib/**.g.dart + - lib/**.graphql.dart linter: # The lint rules applied to this project can be customized in the diff --git a/assets/translations/en.json b/assets/translations/en.json index 4fdb0626..6c48f966 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -163,6 +163,16 @@ "refresh": "Refresh status", "refetchBackups": "Refetch backup list", "refetchingList": "In a few minutes list will be updated" + }, + "storage": { + "card_title": "Server Storage", + "status_ok": "Disk usage is OK", + "status_error": "Low disk space", + "disk_usage": "{} GB used", + "disk_total": "{} GB total ยท {}", + "gb": "{} GB", + "mb": "{} MB", + "extend_volume_button": "Extend volume" } }, "not_ready_card": { diff --git a/lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql b/lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql new file mode 100644 index 00000000..2a566271 --- /dev/null +++ b/lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql @@ -0,0 +1,11 @@ +query GetServerDiskVolumesQuery { + storage { + volumes { + freeSpace + name + root + totalSpace + usedSpace + } + } +} \ No newline at end of file diff --git a/lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql.dart new file mode 100644 index 00000000..007adc55 --- /dev/null +++ b/lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql.dart @@ -0,0 +1,542 @@ +import 'package:gql/ast.dart'; +import 'package:graphql/client.dart' as graphql; +import 'package:json_annotation/json_annotation.dart'; +part 'get_server_disk_volumes.graphql.g.dart'; + +@JsonSerializable(explicitToJson: true) +class Query$GetServerDiskVolumesQuery { + Query$GetServerDiskVolumesQuery( + {required this.storage, required this.$__typename}); + + @override + factory Query$GetServerDiskVolumesQuery.fromJson(Map json) => + _$Query$GetServerDiskVolumesQueryFromJson(json); + + final Query$GetServerDiskVolumesQuery$storage storage; + + @JsonKey(name: '__typename') + final String $__typename; + + Map toJson() => + _$Query$GetServerDiskVolumesQueryToJson(this); + int get hashCode { + final l$storage = storage; + final l$$__typename = $__typename; + return Object.hashAll([l$storage, l$$__typename]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (!(other is Query$GetServerDiskVolumesQuery) || + runtimeType != other.runtimeType) return false; + final l$storage = storage; + final lOther$storage = other.storage; + if (l$storage != lOther$storage) return false; + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) return false; + return true; + } +} + +extension UtilityExtension$Query$GetServerDiskVolumesQuery + on Query$GetServerDiskVolumesQuery { + CopyWith$Query$GetServerDiskVolumesQuery + get copyWith => CopyWith$Query$GetServerDiskVolumesQuery(this, (i) => i); +} + +abstract class CopyWith$Query$GetServerDiskVolumesQuery { + factory CopyWith$Query$GetServerDiskVolumesQuery( + Query$GetServerDiskVolumesQuery instance, + TRes Function(Query$GetServerDiskVolumesQuery) then) = + _CopyWithImpl$Query$GetServerDiskVolumesQuery; + + factory CopyWith$Query$GetServerDiskVolumesQuery.stub(TRes res) = + _CopyWithStubImpl$Query$GetServerDiskVolumesQuery; + + TRes call( + {Query$GetServerDiskVolumesQuery$storage? storage, String? $__typename}); + CopyWith$Query$GetServerDiskVolumesQuery$storage get storage; +} + +class _CopyWithImpl$Query$GetServerDiskVolumesQuery + implements CopyWith$Query$GetServerDiskVolumesQuery { + _CopyWithImpl$Query$GetServerDiskVolumesQuery(this._instance, this._then); + + final Query$GetServerDiskVolumesQuery _instance; + + final TRes Function(Query$GetServerDiskVolumesQuery) _then; + + static const _undefined = {}; + + TRes call({Object? storage = _undefined, Object? $__typename = _undefined}) => + _then(Query$GetServerDiskVolumesQuery( + storage: storage == _undefined || storage == null + ? _instance.storage + : (storage as Query$GetServerDiskVolumesQuery$storage), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String))); + CopyWith$Query$GetServerDiskVolumesQuery$storage get storage { + final local$storage = _instance.storage; + return CopyWith$Query$GetServerDiskVolumesQuery$storage( + local$storage, (e) => call(storage: e)); + } +} + +class _CopyWithStubImpl$Query$GetServerDiskVolumesQuery + implements CopyWith$Query$GetServerDiskVolumesQuery { + _CopyWithStubImpl$Query$GetServerDiskVolumesQuery(this._res); + + TRes _res; + + call( + {Query$GetServerDiskVolumesQuery$storage? storage, + String? $__typename}) => + _res; + CopyWith$Query$GetServerDiskVolumesQuery$storage get storage => + CopyWith$Query$GetServerDiskVolumesQuery$storage.stub(_res); +} + +const documentNodeQueryGetServerDiskVolumesQuery = DocumentNode(definitions: [ + OperationDefinitionNode( + type: OperationType.query, + name: NameNode(value: 'GetServerDiskVolumesQuery'), + variableDefinitions: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'storage'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'volumes'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'freeSpace'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'name'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'root'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'totalSpace'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'usedSpace'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])), +]); +Query$GetServerDiskVolumesQuery _parserFn$Query$GetServerDiskVolumesQuery( + Map data) => + Query$GetServerDiskVolumesQuery.fromJson(data); + +class Options$Query$GetServerDiskVolumesQuery + extends graphql.QueryOptions { + Options$Query$GetServerDiskVolumesQuery( + {String? operationName, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Duration? pollInterval, + graphql.Context? context}) + : super( + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult, + pollInterval: pollInterval, + context: context, + document: documentNodeQueryGetServerDiskVolumesQuery, + parserFn: _parserFn$Query$GetServerDiskVolumesQuery); +} + +class WatchOptions$Query$GetServerDiskVolumesQuery + extends graphql.WatchQueryOptions { + WatchOptions$Query$GetServerDiskVolumesQuery( + {String? operationName, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + graphql.Context? context, + Duration? pollInterval, + bool? eagerlyFetchResults, + bool carryForwardDataOnException = true, + bool fetchResults = false}) + : super( + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult, + context: context, + document: documentNodeQueryGetServerDiskVolumesQuery, + pollInterval: pollInterval, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + fetchResults: fetchResults, + parserFn: _parserFn$Query$GetServerDiskVolumesQuery); +} + +class FetchMoreOptions$Query$GetServerDiskVolumesQuery + extends graphql.FetchMoreOptions { + FetchMoreOptions$Query$GetServerDiskVolumesQuery( + {required graphql.UpdateQuery updateQuery}) + : super( + updateQuery: updateQuery, + document: documentNodeQueryGetServerDiskVolumesQuery); +} + +extension ClientExtension$Query$GetServerDiskVolumesQuery + on graphql.GraphQLClient { + Future> + query$GetServerDiskVolumesQuery( + [Options$Query$GetServerDiskVolumesQuery? options]) async => + await this + .query(options ?? Options$Query$GetServerDiskVolumesQuery()); + graphql.ObservableQuery + watchQuery$GetServerDiskVolumesQuery( + [WatchOptions$Query$GetServerDiskVolumesQuery? options]) => + this.watchQuery( + options ?? WatchOptions$Query$GetServerDiskVolumesQuery()); + void writeQuery$GetServerDiskVolumesQuery( + {required Query$GetServerDiskVolumesQuery data, + bool broadcast = true}) => + this.writeQuery( + graphql.Request( + operation: graphql.Operation( + document: documentNodeQueryGetServerDiskVolumesQuery)), + data: data.toJson(), + broadcast: broadcast); + Query$GetServerDiskVolumesQuery? readQuery$GetServerDiskVolumesQuery( + {bool optimistic = true}) { + final result = this.readQuery( + graphql.Request( + operation: graphql.Operation( + document: documentNodeQueryGetServerDiskVolumesQuery)), + optimistic: optimistic); + return result == null + ? null + : Query$GetServerDiskVolumesQuery.fromJson(result); + } +} + +@JsonSerializable(explicitToJson: true) +class Query$GetServerDiskVolumesQuery$storage { + Query$GetServerDiskVolumesQuery$storage( + {required this.volumes, required this.$__typename}); + + @override + factory Query$GetServerDiskVolumesQuery$storage.fromJson( + Map json) => + _$Query$GetServerDiskVolumesQuery$storageFromJson(json); + + final List volumes; + + @JsonKey(name: '__typename') + final String $__typename; + + Map toJson() => + _$Query$GetServerDiskVolumesQuery$storageToJson(this); + int get hashCode { + final l$volumes = volumes; + final l$$__typename = $__typename; + return Object.hashAll( + [Object.hashAll(l$volumes.map((v) => v)), l$$__typename]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (!(other is Query$GetServerDiskVolumesQuery$storage) || + runtimeType != other.runtimeType) return false; + final l$volumes = volumes; + final lOther$volumes = other.volumes; + if (l$volumes.length != lOther$volumes.length) return false; + for (int i = 0; i < l$volumes.length; i++) { + final l$volumes$entry = l$volumes[i]; + final lOther$volumes$entry = lOther$volumes[i]; + if (l$volumes$entry != lOther$volumes$entry) return false; + } + + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) return false; + return true; + } +} + +extension UtilityExtension$Query$GetServerDiskVolumesQuery$storage + on Query$GetServerDiskVolumesQuery$storage { + CopyWith$Query$GetServerDiskVolumesQuery$storage< + Query$GetServerDiskVolumesQuery$storage> + get copyWith => + CopyWith$Query$GetServerDiskVolumesQuery$storage(this, (i) => i); +} + +abstract class CopyWith$Query$GetServerDiskVolumesQuery$storage { + factory CopyWith$Query$GetServerDiskVolumesQuery$storage( + Query$GetServerDiskVolumesQuery$storage instance, + TRes Function(Query$GetServerDiskVolumesQuery$storage) then) = + _CopyWithImpl$Query$GetServerDiskVolumesQuery$storage; + + factory CopyWith$Query$GetServerDiskVolumesQuery$storage.stub(TRes res) = + _CopyWithStubImpl$Query$GetServerDiskVolumesQuery$storage; + + TRes call( + {List? volumes, + String? $__typename}); + TRes volumes( + Iterable Function( + Iterable< + CopyWith$Query$GetServerDiskVolumesQuery$storage$volumes< + Query$GetServerDiskVolumesQuery$storage$volumes>>) + _fn); +} + +class _CopyWithImpl$Query$GetServerDiskVolumesQuery$storage + implements CopyWith$Query$GetServerDiskVolumesQuery$storage { + _CopyWithImpl$Query$GetServerDiskVolumesQuery$storage( + this._instance, this._then); + + final Query$GetServerDiskVolumesQuery$storage _instance; + + final TRes Function(Query$GetServerDiskVolumesQuery$storage) _then; + + static const _undefined = {}; + + TRes call({Object? volumes = _undefined, Object? $__typename = _undefined}) => + _then(Query$GetServerDiskVolumesQuery$storage( + volumes: volumes == _undefined || volumes == null + ? _instance.volumes + : (volumes + as List), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String))); + TRes volumes( + Iterable Function( + Iterable< + CopyWith$Query$GetServerDiskVolumesQuery$storage$volumes< + Query$GetServerDiskVolumesQuery$storage$volumes>>) + _fn) => + call( + volumes: _fn(_instance.volumes.map((e) => + CopyWith$Query$GetServerDiskVolumesQuery$storage$volumes( + e, (i) => i))).toList()); +} + +class _CopyWithStubImpl$Query$GetServerDiskVolumesQuery$storage + implements CopyWith$Query$GetServerDiskVolumesQuery$storage { + _CopyWithStubImpl$Query$GetServerDiskVolumesQuery$storage(this._res); + + TRes _res; + + call( + {List? volumes, + String? $__typename}) => + _res; + volumes(_fn) => _res; +} + +@JsonSerializable(explicitToJson: true) +class Query$GetServerDiskVolumesQuery$storage$volumes { + Query$GetServerDiskVolumesQuery$storage$volumes( + {required this.freeSpace, + required this.name, + required this.root, + required this.totalSpace, + required this.usedSpace, + required this.$__typename}); + + @override + factory Query$GetServerDiskVolumesQuery$storage$volumes.fromJson( + Map json) => + _$Query$GetServerDiskVolumesQuery$storage$volumesFromJson(json); + + final String freeSpace; + + final String name; + + final bool root; + + final String totalSpace; + + final String usedSpace; + + @JsonKey(name: '__typename') + final String $__typename; + + Map toJson() => + _$Query$GetServerDiskVolumesQuery$storage$volumesToJson(this); + int get hashCode { + final l$freeSpace = freeSpace; + final l$name = name; + final l$root = root; + final l$totalSpace = totalSpace; + final l$usedSpace = usedSpace; + final l$$__typename = $__typename; + return Object.hashAll([ + l$freeSpace, + l$name, + l$root, + l$totalSpace, + l$usedSpace, + l$$__typename + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (!(other is Query$GetServerDiskVolumesQuery$storage$volumes) || + runtimeType != other.runtimeType) return false; + final l$freeSpace = freeSpace; + final lOther$freeSpace = other.freeSpace; + if (l$freeSpace != lOther$freeSpace) return false; + final l$name = name; + final lOther$name = other.name; + if (l$name != lOther$name) return false; + final l$root = root; + final lOther$root = other.root; + if (l$root != lOther$root) return false; + final l$totalSpace = totalSpace; + final lOther$totalSpace = other.totalSpace; + if (l$totalSpace != lOther$totalSpace) return false; + final l$usedSpace = usedSpace; + final lOther$usedSpace = other.usedSpace; + if (l$usedSpace != lOther$usedSpace) return false; + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) return false; + return true; + } +} + +extension UtilityExtension$Query$GetServerDiskVolumesQuery$storage$volumes + on Query$GetServerDiskVolumesQuery$storage$volumes { + CopyWith$Query$GetServerDiskVolumesQuery$storage$volumes< + Query$GetServerDiskVolumesQuery$storage$volumes> + get copyWith => CopyWith$Query$GetServerDiskVolumesQuery$storage$volumes( + this, (i) => i); +} + +abstract class CopyWith$Query$GetServerDiskVolumesQuery$storage$volumes { + factory CopyWith$Query$GetServerDiskVolumesQuery$storage$volumes( + Query$GetServerDiskVolumesQuery$storage$volumes instance, + TRes Function(Query$GetServerDiskVolumesQuery$storage$volumes) then) = + _CopyWithImpl$Query$GetServerDiskVolumesQuery$storage$volumes; + + factory CopyWith$Query$GetServerDiskVolumesQuery$storage$volumes.stub( + TRes res) = + _CopyWithStubImpl$Query$GetServerDiskVolumesQuery$storage$volumes; + + TRes call( + {String? freeSpace, + String? name, + bool? root, + String? totalSpace, + String? usedSpace, + String? $__typename}); +} + +class _CopyWithImpl$Query$GetServerDiskVolumesQuery$storage$volumes + implements CopyWith$Query$GetServerDiskVolumesQuery$storage$volumes { + _CopyWithImpl$Query$GetServerDiskVolumesQuery$storage$volumes( + this._instance, this._then); + + final Query$GetServerDiskVolumesQuery$storage$volumes _instance; + + final TRes Function(Query$GetServerDiskVolumesQuery$storage$volumes) _then; + + static const _undefined = {}; + + TRes call( + {Object? freeSpace = _undefined, + Object? name = _undefined, + Object? root = _undefined, + Object? totalSpace = _undefined, + Object? usedSpace = _undefined, + Object? $__typename = _undefined}) => + _then(Query$GetServerDiskVolumesQuery$storage$volumes( + freeSpace: freeSpace == _undefined || freeSpace == null + ? _instance.freeSpace + : (freeSpace as String), + name: name == _undefined || name == null + ? _instance.name + : (name as String), + root: root == _undefined || root == null + ? _instance.root + : (root as bool), + totalSpace: totalSpace == _undefined || totalSpace == null + ? _instance.totalSpace + : (totalSpace as String), + usedSpace: usedSpace == _undefined || usedSpace == null + ? _instance.usedSpace + : (usedSpace as String), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String))); +} + +class _CopyWithStubImpl$Query$GetServerDiskVolumesQuery$storage$volumes + implements CopyWith$Query$GetServerDiskVolumesQuery$storage$volumes { + _CopyWithStubImpl$Query$GetServerDiskVolumesQuery$storage$volumes(this._res); + + TRes _res; + + call( + {String? freeSpace, + String? name, + bool? root, + String? totalSpace, + String? usedSpace, + String? $__typename}) => + _res; +} diff --git a/lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql.g.dart b/lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql.g.dart new file mode 100644 index 00000000..ecbd5a02 --- /dev/null +++ b/lib/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql.g.dart @@ -0,0 +1,64 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'get_server_disk_volumes.graphql.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Query$GetServerDiskVolumesQuery _$Query$GetServerDiskVolumesQueryFromJson( + Map json) => + Query$GetServerDiskVolumesQuery( + storage: Query$GetServerDiskVolumesQuery$storage.fromJson( + json['storage'] as Map), + $__typename: json['__typename'] as String, + ); + +Map _$Query$GetServerDiskVolumesQueryToJson( + Query$GetServerDiskVolumesQuery instance) => + { + 'storage': instance.storage.toJson(), + '__typename': instance.$__typename, + }; + +Query$GetServerDiskVolumesQuery$storage + _$Query$GetServerDiskVolumesQuery$storageFromJson( + Map json) => + Query$GetServerDiskVolumesQuery$storage( + volumes: (json['volumes'] as List) + .map((e) => + Query$GetServerDiskVolumesQuery$storage$volumes.fromJson( + e as Map)) + .toList(), + $__typename: json['__typename'] as String, + ); + +Map _$Query$GetServerDiskVolumesQuery$storageToJson( + Query$GetServerDiskVolumesQuery$storage instance) => + { + 'volumes': instance.volumes.map((e) => e.toJson()).toList(), + '__typename': instance.$__typename, + }; + +Query$GetServerDiskVolumesQuery$storage$volumes + _$Query$GetServerDiskVolumesQuery$storage$volumesFromJson( + Map json) => + Query$GetServerDiskVolumesQuery$storage$volumes( + freeSpace: json['freeSpace'] as String, + name: json['name'] as String, + root: json['root'] as bool, + totalSpace: json['totalSpace'] as String, + usedSpace: json['usedSpace'] as String, + $__typename: json['__typename'] as String, + ); + +Map _$Query$GetServerDiskVolumesQuery$storage$volumesToJson( + Query$GetServerDiskVolumesQuery$storage$volumes instance) => + { + 'freeSpace': instance.freeSpace, + 'name': instance.name, + 'root': instance.root, + 'totalSpace': instance.totalSpace, + 'usedSpace': instance.usedSpace, + '__typename': instance.$__typename, + }; diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql b/lib/logic/api_maps/graphql_maps/schema/schema.graphql index c4b3246d..3ad04e3f 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql @@ -7,6 +7,18 @@ type Alert { timestamp: DateTime } +type Storage { + volumes: [StorageVolume!]! +} + +type StorageVolume { + freeSpace: String! + name: String! + root: Boolean! + totalSpace: String! + usedSpace: String! +} + type Api { version: String! devices: [ApiDevice!]! @@ -82,6 +94,7 @@ interface MutationReturnInterface { type Query { system: System! + storage: Storage! api: Api! } diff --git a/lib/logic/api_maps/graphql_maps/schema/server.dart b/lib/logic/api_maps/graphql_maps/schema/server.dart index 1ddf49f7..ab4fe197 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server.dart @@ -3,8 +3,10 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/api_map.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/get_api_tokens.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/get_api_version.graphql.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/get_server_disk_volumes.graphql.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/api_token.dart'; +import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; class ServerApi extends ApiMap { ServerApi({ @@ -54,4 +56,21 @@ class ServerApi extends ApiMap { return tokens; } + + Future> getServerDiskVolumes() async { + QueryResult response; + List volumes = []; + + try { + final GraphQLClient client = await getClient(); + response = await client.query$GetServerDiskVolumesQuery(); + volumes = response.data!['storage']['volumes'] + .map((final e) => ServerDiskVolume.fromJson(e)) + .toList(); + } catch (e) { + print(e); + } + + return volumes; + } } diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 6899240f..4ed0d382 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -4,12 +4,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider_factory.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/provider_api_settings.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_credential.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_repository.dart'; @@ -553,6 +555,10 @@ class ServerInstallationCubit extends Cubit { ); } + Future> getServerDiskVolumes() async => + ServerApi(authToken: 'HARDCODE OUR BEARER HERE FOR NOW') + .getServerDiskVolumes(); + Future setAndValidateCloudflareToken(final String token) async { final ServerInstallationRecovery dataState = state as ServerInstallationRecovery; diff --git a/lib/logic/models/json/server_disk_volume.dart b/lib/logic/models/json/server_disk_volume.dart new file mode 100644 index 00000000..b7d4bfca --- /dev/null +++ b/lib/logic/models/json/server_disk_volume.dart @@ -0,0 +1,22 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'server_disk_volume.g.dart'; + +@JsonSerializable() +class ServerDiskVolume { + factory ServerDiskVolume.fromJson(final Map json) => + _$ServerDiskVolumeFromJson(json); + ServerDiskVolume({ + required this.freeSpace, + required this.name, + required this.root, + required this.totalSpace, + required this.usedSpace, + }); + + final String freeSpace; + final String name; + final bool root; + final String totalSpace; + final String usedSpace; +} diff --git a/lib/logic/models/json/server_disk_volume.g.dart b/lib/logic/models/json/server_disk_volume.g.dart new file mode 100644 index 00000000..95f5788b --- /dev/null +++ b/lib/logic/models/json/server_disk_volume.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'server_disk_volume.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ServerDiskVolume _$ServerDiskVolumeFromJson(Map json) => + ServerDiskVolume( + freeSpace: json['freeSpace'] as String, + name: json['name'] as String, + root: json['root'] as bool, + totalSpace: json['totalSpace'] as String, + usedSpace: json['usedSpace'] as String, + ); + +Map _$ServerDiskVolumeToJson(ServerDiskVolume instance) => + { + 'freeSpace': instance.freeSpace, + 'name': instance.name, + 'root': instance.root, + 'totalSpace': instance.totalSpace, + 'usedSpace': instance.usedSpace, + }; diff --git a/lib/logic/models/state_types.dart b/lib/logic/models/state_types.dart index cd79de6c..3fb110c0 100644 --- a/lib/logic/models/state_types.dart +++ b/lib/logic/models/state_types.dart @@ -1 +1 @@ -enum StateType { uninitialized, stable, warning } +enum StateType { uninitialized, stable, warning, error } diff --git a/lib/ui/components/brand_linear_indicator/brand_linear_indicator.dart b/lib/ui/components/brand_linear_indicator/brand_linear_indicator.dart new file mode 100644 index 00000000..807d3224 --- /dev/null +++ b/lib/ui/components/brand_linear_indicator/brand_linear_indicator.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class BrandLinearIndicator extends StatelessWidget { + const BrandLinearIndicator({ + required this.value, + required this.color, + required this.backgroundColor, + required this.height, + final super.key, + }); + + final double value; + final Color color; + final Color backgroundColor; + final double height; + + @override + Widget build(final BuildContext context) => Container( + height: height, + width: double.infinity, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(height), + ), + alignment: Alignment.centerLeft, + child: FractionallySizedBox( + widthFactor: value, + child: Container( + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(height), + ), + ), + ), + ); +} diff --git a/lib/ui/components/icon_status_mask/icon_status_mask.dart b/lib/ui/components/icon_status_mask/icon_status_mask.dart index 0c507ede..cda75049 100644 --- a/lib/ui/components/icon_status_mask/icon_status_mask.dart +++ b/lib/ui/components/icon_status_mask/icon_status_mask.dart @@ -28,6 +28,12 @@ class IconStatusMask extends StatelessWidget { case StateType.warning: colors = BrandColors.warningGradientColors; break; + case StateType.error: + colors = [ + Theme.of(context).colorScheme.error, + Theme.of(context).colorScheme.error, + ]; + break; } return ShaderMask( shaderCallback: (final bounds) => LinearGradient( diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 97e4aeeb..36903d1f 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -5,6 +5,7 @@ import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; import 'package:selfprivacy/logic/models/provider.dart'; import 'package:selfprivacy/ui/components/brand_bottom_sheet/brand_bottom_sheet.dart'; import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart'; @@ -15,6 +16,7 @@ import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; import 'package:selfprivacy/ui/pages/backup_details/backup_details.dart'; import 'package:selfprivacy/ui/pages/dns_details/dns_details.dart'; +import 'package:selfprivacy/ui/pages/providers/storage_card.dart'; import 'package:selfprivacy/ui/pages/server_details/server_details_screen.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart'; @@ -67,6 +69,23 @@ class _ProvidersPageState extends State { ), ) .toList(); + cards.add( + Padding( + padding: const EdgeInsets.only(bottom: 30), + child: FutureBuilder( + future: + context.read().getServerDiskVolumes(), + builder: ( + final BuildContext context, + final AsyncSnapshot snapshot, + ) => + StorageCard( + volumes: + snapshot.hasData ? snapshot.data as List : [], + ), + ), + ), + ); return Scaffold( appBar: PreferredSize( preferredSize: const Size.fromHeight(52), diff --git a/lib/ui/pages/providers/storage_card.dart b/lib/ui/pages/providers/storage_card.dart new file mode 100644 index 00000000..51c510e4 --- /dev/null +++ b/lib/ui/pages/providers/storage_card.dart @@ -0,0 +1,152 @@ +import 'dart:ffi'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; +import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; +import 'package:selfprivacy/ui/components/brand_cards/brand_cards.dart'; +import 'package:selfprivacy/ui/components/brand_linear_indicator/brand_linear_indicator.dart'; +import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; + +class DiskVolume { + int gbUsed = 0; + int gbTotal = 0; + String name = ''; + bool root = false; + + /// from 0.0 to 1.0 + double percentage = 0.0; +} + +class DiskStatus { + bool isDiskOkay = false; + List diskVolumes = []; +} + +class StorageCard extends StatelessWidget { + const StorageCard({required this.volumes, final super.key}); + + final List volumes; + + @override + Widget build( + final BuildContext context, + ) { + final DiskStatus diskStatus = toDiskStatus(volumes); + + final List sections = []; + for (final DiskVolume volume in diskStatus.diskVolumes) { + sections.add( + const SizedBox(height: 16), + ); + sections.add( + Text( + 'providers.storage.disk_usage'.tr(args: [volume.gbUsed.toString()]), + style: Theme.of(context).textTheme.titleMedium, + ), + ); + sections.add( + const SizedBox(height: 4), + ); + sections.add( + BrandLinearIndicator( + value: volume.percentage, + color: volume.root + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.secondary, + backgroundColor: Theme.of(context).colorScheme.surfaceVariant, + height: 14.0, + ), + ); + sections.add( + const SizedBox(height: 4), + ); + sections.add( + Text( + 'providers.storage.disk_total'.tr(args: [ + volume.gbTotal.toString(), + volume.name, + ]), + style: Theme.of(context).textTheme.bodySmall, + ), + ); + } + + return GestureDetector( + onTap: null, + child: BrandCards.big( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const IconStatusMask( + status: StateType.stable, + child: Icon( + Icons.storage_outlined, + size: 30, + color: Colors.white, + ), + ), + IconStatusMask( + status: StateType.stable, + child: Icon( + diskStatus.isDiskOkay + ? Icons.check_circle_outline + : Icons.error_outline, + size: 24, + color: Colors.white, + ), + ), + ], + ), + const SizedBox(height: 16), + Text( + 'providers.storage.card_title'.tr(), + style: Theme.of(context).textTheme.titleLarge, + ), + Text( + diskStatus.isDiskOkay + ? 'providers.storage.status_ok'.tr() + : 'providers.storage.status_error'.tr(), + style: Theme.of(context).textTheme.bodyLarge, + ), + ...sections, + const SizedBox(height: 8), + ], + ), + ), + ); + } + + DiskStatus toDiskStatus(final List status) { + final DiskStatus diskStatus = DiskStatus(); + diskStatus.isDiskOkay = true; + + diskStatus.diskVolumes = status.map(( + final ServerDiskVolume volume, + ) { + final DiskVolume diskVolume = DiskVolume(); + diskVolume.gbUsed = volume.usedSpace == 'None' + ? 0 + : int.parse(volume.usedSpace) ~/ 1000000000; + diskVolume.gbTotal = volume.totalSpace == 'None' + ? 0 + : int.parse(volume.totalSpace) ~/ 1000000000; + diskVolume.name = volume.name; + diskVolume.root = volume.root; + diskVolume.percentage = + volume.usedSpace != 'None' && volume.totalSpace != 'None' + ? 1.0 / int.parse(volume.totalSpace) * int.parse(volume.usedSpace) + : 0.0; + if (diskVolume.percentage >= 0.8 || + diskVolume.gbTotal - diskVolume.gbUsed <= 2) { + diskStatus.isDiskOkay = false; + } + return diskVolume; + }).toList(); + + return diskStatus; + } +}