├── cmd ├── desktop │ └── desktop.go ├── mobile │ └── mobile.go ├── main.go └── helpers.go ├── lib ├── common │ ├── resources │ │ ├── deployments.dart │ │ ├── pods.dart │ │ └── resources.dart │ ├── types.dart │ ├── secrets.dart.gpg │ ├── const.dart │ ├── ops.dart │ └── styles.dart ├── firebase_options.dart.gpg ├── pages │ ├── settings │ │ ├── apikey.dart │ │ └── locale.dart │ ├── not_found.dart │ ├── k8s_list │ │ └── cluster │ │ │ ├── create.dart │ │ │ └── select_clusters.dart │ ├── workloads.dart │ ├── resources.dart │ └── k8s_detail │ │ └── yaml_page.dart ├── providers │ ├── talker.dart │ ├── timeout.dart │ ├── theme.dart │ ├── revenuecat_customer.dart │ ├── lang.dart │ └── current_cluster.dart ├── services │ ├── revenuecat.dart │ ├── stash.dart │ └── k8z_service.dart ├── widgets │ ├── inherited.dart │ ├── modal.dart │ ├── set_api_timeout.dart │ ├── context_provider.dart │ ├── native_dialog.dart │ ├── settings_tile.dart │ ├── tiles.dart │ ├── widgets.dart │ ├── detail_widgets │ │ ├── services.dart │ │ ├── configmap.dart │ │ └── secret.dart │ └── virtual_keyboard.dart ├── models │ ├── helm_release.dart │ └── kubernetes │ │ ├── helper.dart │ │ ├── io_k8s_api_discovery_v1_for_zone.dart │ │ ├── io_k8s_api_core_v1_daemon_endpoint.dart │ │ ├── io_k8s_api_node_v1_overhead.dart │ │ ├── io_k8s_api_core_v1_limit_range_spec.dart │ │ ├── io_k8s_api_core_v1_sysctl.dart │ │ ├── io_k8s_api_core_v1_pod_os.dart │ │ ├── io_k8s_api_core_v1_http_header.dart │ │ ├── io_k8s_api_core_v1_pod_readiness_gate.dart │ │ ├── io_k8s_api_core_v1_node_selector.dart │ │ ├── io_k8s_api_storage_v1_csi_node_spec.dart │ │ ├── io_k8s_api_core_v1_scope_selector.dart │ │ ├── io_k8s_api_core_v1_downward_api_projection.dart │ │ └── io_k8s_api_flowcontrol_v1beta1_user_subject.dart ├── theme │ └── kung.dart ├── dao │ ├── dao.dart │ └── kube.dart └── generated │ └── intl │ └── messages_all.dart ├── assets ├── Icon2x.png ├── icon@8x.png └── fonts │ └── DroidSansMNerdFontMono-Regular.otf ├── images └── icon │ └── k8s-1.29.png ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift ├── .gitignore ├── Podfile └── libs │ └── libk8z.h ├── macos ├── installer-background.png ├── Runner │ ├── Configs │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── Warnings.xcconfig │ │ └── AppInfo.xcconfig │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_64.png │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_512.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── MainFlutterWindow.swift │ ├── Release.entitlements │ ├── DebugProfile.entitlements │ └── Info.plist ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── .gitignore ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── RunnerTests │ └── RunnerTests.swift └── Podfile ├── tools.go ├── hack ├── clangwrap.sh ├── check_static_link.sh ├── codefix.sh └── build_macos_dmg.sh ├── gopkg ├── k8z │ ├── models │ │ └── ephemeral.go │ └── client.go └── local │ └── logsream.go ├── .metadata ├── .gitignore ├── .kiro └── specs │ └── google-analytics-page-title-fix │ └── requirements.md ├── analysis_options.yaml ├── README.md ├── Makefile ├── CHANGELOG.md └── go.mod /cmd/desktop/desktop.go: -------------------------------------------------------------------------------- 1 | package desktop 2 | -------------------------------------------------------------------------------- /cmd/mobile/mobile.go: -------------------------------------------------------------------------------- 1 | package mobile 2 | -------------------------------------------------------------------------------- /lib/common/resources/deployments.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() {} 4 | -------------------------------------------------------------------------------- /assets/Icon2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/assets/Icon2x.png -------------------------------------------------------------------------------- /assets/icon@8x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/assets/icon@8x.png -------------------------------------------------------------------------------- /lib/common/types.dart: -------------------------------------------------------------------------------- 1 | enum ShellType { 2 | pod, 3 | debug, 4 | node, 5 | } 6 | -------------------------------------------------------------------------------- /images/icon/k8s-1.29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/images/icon/k8s-1.29.png -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | #import "libk8z.h" 3 | -------------------------------------------------------------------------------- /lib/common/secrets.dart.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/lib/common/secrets.dart.gpg -------------------------------------------------------------------------------- /lib/firebase_options.dart.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/lib/firebase_options.dart.gpg -------------------------------------------------------------------------------- /macos/installer-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/macos/installer-background.png -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /assets/fonts/DroidSansMNerdFontMono-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/assets/fonts/DroidSansMNerdFontMono-Regular.otf -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8zdev/k8z/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | 9 | # Go build 10 | k8z.arm64.dylib 11 | k8z.arm64.h 12 | k8z.x64.dylib 13 | k8z.x64.h 14 | k8z.dylib 15 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | // Package tools is used to force Go modules to download and install our dependencies used for our build pipeline. 5 | package tools 6 | 7 | import ( 8 | _ "golang.org/x/mobile/cmd/gomobile" 9 | ) 10 | -------------------------------------------------------------------------------- /cmd/helpers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // #include 4 | // #include "stdint.h" 5 | import "C" 6 | 7 | func cstr2String(pointer *C.char, size C.int) string { 8 | if size == 0 || pointer == nil { 9 | return "" 10 | } 11 | return C.GoStringN(pointer, size) 12 | } 13 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /hack/clangwrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # go/clangwrap.sh 4 | 5 | SDK_PATH=$(xcrun --sdk $SDK --show-sdk-path) 6 | CLANG=$(xcrun --sdk $SDK --find clang) 7 | 8 | if [ "$GOARCH" == "amd64" ]; then 9 | CARCH="x86_64" 10 | elif [ "$GOARCH" == "arm64" ]; then 11 | CARCH="arm64" 12 | fi 13 | 14 | exec $CLANG -arch $CARCH -isysroot $SDK_PATH -mios-version-min=10.0 "$@" 15 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /lib/pages/settings/apikey.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ApikeySettingPage extends StatefulWidget { 4 | const ApikeySettingPage({super.key}); 5 | 6 | @override 7 | State createState() => _ApikeySettingPageState(); 8 | } 9 | 10 | class _ApikeySettingPageState extends State { 11 | @override 12 | Widget build(BuildContext context) { 13 | return const Center(child: Text("api key settings")); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/providers/talker.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:k8zdev/services/stash.dart'; 3 | 4 | const String key = "app_talker_mode"; 5 | 6 | class TalkerModeProvider with ChangeNotifier { 7 | bool? _enabled; 8 | 9 | bool? get enabled => _enabled; 10 | 11 | init() async { 12 | _enabled = await vget(key) ?? false; 13 | } 14 | 15 | void changeMode(mode) async { 16 | _enabled = mode; 17 | vset(key, mode); 18 | notifyListeners(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/providers/timeout.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:k8zdev/services/stash.dart'; 3 | 4 | const String key = "k8s_api_timeout"; 5 | 6 | class TimeoutProvider extends ChangeNotifier { 7 | int _timeout = 30; 8 | 9 | int get timeout => _timeout; 10 | 11 | init() async { 12 | var timeout = await vget(key) ?? 30; 13 | _timeout = timeout; 14 | } 15 | 16 | void update(int seconds) { 17 | _timeout = seconds; 18 | vset(key, seconds); 19 | notifyListeners(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hack/check_static_link.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | GRN='\x1B[32m' 3 | END='\x1B[0m' 4 | 5 | files=("build/ios/Release-iphoneos/Runner.app/Runner" "build/ios/iphoneos/Runner.app/Runner") 6 | 7 | for file in ${files[@]}; do 8 | echo -e "${GRN}check binary file ${file}${END}" 9 | bash -c "nm ${file} | egrep 'T _PinStatic'" 10 | bash -c "nm ${file} | egrep 'T _K8zRequest'" 11 | bash -c "nm ${file} | egrep 'T _FreePointer'" 12 | bash -c "nm ${file} | egrep 'T _LocalServerAddr'" 13 | bash -c "nm ${file} | egrep 'T _StartLocalServer'" 14 | done 15 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | com.apple.security.network.client 12 | 13 | com.apple.security.network.server 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lib/providers/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:k8zdev/services/stash.dart'; 3 | 4 | const String key = "app_theme_mode"; 5 | 6 | class ThemeModeProvider with ChangeNotifier { 7 | ThemeMode? _mode; 8 | 9 | ThemeMode? get mode => _mode; 10 | 11 | init() async { 12 | var modeIdx = await vget(key) ?? 0; 13 | _mode = ThemeMode.values[modeIdx]; 14 | } 15 | 16 | void changeMode(ThemeMode mode) async { 17 | _mode = mode; 18 | vset(key, mode.index); 19 | notifyListeners(); 20 | } 21 | 22 | bool isCurrent(ThemeMode mode) => _mode == mode; 23 | } 24 | -------------------------------------------------------------------------------- /lib/services/revenuecat.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:k8zdev/common/secrets.dart'; 5 | import 'package:purchases_flutter/purchases_flutter.dart'; 6 | 7 | Future initRevenueCatState() async { 8 | var level = kDebugMode || kProfileMode ? LogLevel.info : LogLevel.verbose; 9 | await Purchases.setLogLevel(level); 10 | 11 | late PurchasesConfiguration config; 12 | if (Platform.isIOS || Platform.isMacOS) { 13 | config = PurchasesConfiguration(revenueCatApplePublicKey); 14 | } 15 | 16 | await Purchases.configure(config); 17 | } 18 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = k8z 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = dev.k8z.app 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2024 k8z.dev All rights reserved. 15 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /lib/widgets/inherited.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:connectivity_plus/connectivity_plus.dart'; 3 | 4 | const noneNetwork = ConnectivityResult.none; 5 | 6 | class AppScope extends InheritedWidget { 7 | final ConnectivityResult networkStatus; 8 | 9 | const AppScope({ 10 | super.key, 11 | required this.networkStatus, 12 | required super.child, 13 | }); 14 | 15 | static AppScope? of(BuildContext context) { 16 | return context.dependOnInheritedWidgetOfExactType(); 17 | } 18 | 19 | bool get offline { 20 | return networkStatus == ConnectivityResult.none; 21 | } 22 | 23 | @override 24 | bool updateShouldNotify(AppScope oldWidget) { 25 | return networkStatus != oldWidget.networkStatus; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/pages/not_found.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NotFoundPage extends StatefulWidget { 4 | final String title; 5 | final String info; 6 | const NotFoundPage({super.key, required this.title, required this.info}); 7 | 8 | @override 9 | State createState() => _NotFoundPageState(); 10 | } 11 | 12 | class _NotFoundPageState extends State { 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | appBar: AppBar(title: Text(widget.title)), 17 | body: Center( 18 | child: TextButton.icon( 19 | onPressed: null, 20 | icon: const Icon(Icons.not_interested_rounded), 21 | label: Text(widget.info), 22 | ), 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gopkg/k8z/models/ephemeral.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type EphemeralPatch struct { 4 | Spec EphemeralPatchSpec `json:"spec"` 5 | } 6 | 7 | type EphemeralPatchSpec struct { 8 | Ephemeralcontainers []Ephemeralcontainer `json:"ephemeralContainers"` 9 | } 10 | 11 | type Ephemeralcontainer struct { 12 | Name string `json:"name"` 13 | Command []string `json:"command"` 14 | Image string `json:"image"` 15 | Targetcontainername string `json:"targetContainerName"` 16 | Stdin bool `json:"stdin"` 17 | Tty bool `json:"tty"` 18 | Volumemounts []struct { 19 | Mountpath string `json:"mountPath"` 20 | Name string `json:"name"` 21 | Readonly bool `json:"readOnly"` 22 | } `json:"volumeMounts"` 23 | } 24 | -------------------------------------------------------------------------------- /lib/services/stash.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path/path.dart'; 4 | import 'package:sqflite/sqflite.dart'; 5 | import 'package:stash/stash_api.dart'; 6 | import 'package:stash_sqlite/stash_sqlite.dart'; 7 | 8 | late Vault vault; 9 | late Store store; 10 | 11 | Future initStash() async { 12 | var databasesPath = await getDatabasesPath(); 13 | var file = File(join(databasesPath, 'vault.db')); 14 | final store = await newSqliteLocalVaultStore(file: file); 15 | vault = await store.vault(name: "k8z.config"); 16 | } 17 | 18 | vset(String key, dynamic value) { 19 | Future.delayed(Duration.zero, () async { 20 | await vault.put(key, value); 21 | }); 22 | } 23 | 24 | vget(String key) async { 25 | T? value = await vault.get(key); 26 | return value; 27 | } 28 | -------------------------------------------------------------------------------- /lib/common/const.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | const minWinSize = Size(375, 667); 5 | const initWinSize = Size(600, 667); 6 | const bottomEdge = EdgeInsets.only(bottom: 60); 7 | const releaseMode = kDebugMode 8 | ? "debug" 9 | : kReleaseMode 10 | ? "release" 11 | : "profile"; 12 | final privacyUrl = Uri( 13 | scheme: "https", 14 | host: "k8z.dev", 15 | path: "/docs/privacy/", 16 | ); 17 | 18 | final docUrl = Uri( 19 | scheme: "https", 20 | host: "k8z.dev", 21 | path: "/docs/", 22 | query: "_f=app", 23 | ); 24 | 25 | final stdeulaUrl = Uri( 26 | scheme: "https", 27 | host: "www.apple.com", 28 | path: "/legal/internet-services/itunes/dev/stdeula/", 29 | ); 30 | 31 | final githubUrl = Uri( 32 | scheme: "https", 33 | host: "github.com", 34 | path: "/k8zdev/k8z", 35 | ); 36 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 13.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | // WORKROUND: keep symbols of static library libk8z.a 11 | print("FreePointer address: \(String(describing: K8zRequest))") 12 | print("FreePointer address: \(String(describing: FreePointer))") 13 | print("FreePointer address: \(String(describing: LocalServerAddr))") 14 | print("FreePointer address: \(String(describing: StartLocalServer))") 15 | print("FreePointer address: \(String(describing: Json2yaml))") 16 | 17 | GeneratedPluginRegistrant.register(with: self) 18 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/models/helm_release.dart: -------------------------------------------------------------------------------- 1 | class Release { 2 | final String name; 3 | final String namespace; 4 | final String revision; 5 | final String updated; 6 | final String status; 7 | final String chart; 8 | final String appVersion; 9 | final String? description; 10 | 11 | Release({ 12 | required this.name, 13 | required this.namespace, 14 | required this.revision, 15 | required this.updated, 16 | required this.status, 17 | required this.chart, 18 | required this.appVersion, 19 | this.description, 20 | }); 21 | 22 | factory Release.fromJson(Map json) { 23 | return Release( 24 | name: json['name'], 25 | namespace: json['namespace'], 26 | revision: json['revision'], 27 | updated: json['updated'], 28 | status: json['status'], 29 | chart: json['chart'], 30 | appVersion: json['appVersion'], 31 | description: json['description'], 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/widgets/modal.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void showModal(BuildContext context, Widget widget, 4 | {int minHeight = 400, double? height}) { 5 | if (minHeight < 100) { 6 | minHeight = 100; 7 | } 8 | showModalBottomSheet( 9 | context: context, 10 | isDismissible: true, 11 | showDragHandle: true, 12 | useRootNavigator: true, 13 | isScrollControlled: true, 14 | shape: const RoundedRectangleBorder( 15 | borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)), 16 | ), 17 | builder: (BuildContext context) { 18 | return Container( 19 | height: height ?? minHeight.toDouble(), 20 | constraints: BoxConstraints( 21 | minHeight: minHeight.toDouble(), 22 | minWidth: MediaQuery.of(context).size.width, 23 | maxHeight: MediaQuery.of(context).size.height * 0.8, 24 | ), 25 | child: widget, 26 | ); 27 | }, 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /lib/common/ops.dart: -------------------------------------------------------------------------------- 1 | import 'package:talker_flutter/talker_flutter.dart'; 2 | import 'package:firebase_crashlytics/firebase_crashlytics.dart'; 3 | 4 | class CrashlyticsTalkerObserver extends TalkerObserver { 5 | CrashlyticsTalkerObserver(); 6 | 7 | @override 8 | void onError(err) { 9 | FirebaseCrashlytics.instance.recordError( 10 | err.error, 11 | err.stackTrace, 12 | reason: err.message, 13 | ); 14 | } 15 | 16 | @override 17 | void onException(err) { 18 | FirebaseCrashlytics.instance.recordError( 19 | err.exception, 20 | err.stackTrace, 21 | reason: err.message, 22 | ); 23 | } 24 | } 25 | 26 | final crashlyticsTalkerObserver = CrashlyticsTalkerObserver(); 27 | final logger = TalkerLogger( 28 | settings: TalkerLoggerSettings( 29 | // Set current logging level 30 | level: LogLevel.debug, 31 | ), 32 | ); 33 | final talker = TalkerFlutter.init( 34 | logger: logger, 35 | observer: crashlyticsTalkerObserver, 36 | ); 37 | -------------------------------------------------------------------------------- /hack/codefix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | K8SMODELPATH=${1:-lib/models/kubernetes} 6 | 7 | echo "model path: ${K8SMODELPATH}\n" 8 | 9 | rm -fr ${K8SMODELPATH}/io_*.dart && cp tmp/lib/model/io_* ${K8SMODELPATH} 10 | find ${K8SMODELPATH} -type f -name 'io_*' -exec sed -i '' 's/openapi\.api/models\.k8s/g' {} + 11 | find "$K8SMODELPATH" -type f -name 'io_*.dart' -exec sh -c 'echo "part '\''kubernetes/$(echo "{}" | sed -e "s|^'$K8SMODELPATH'/||")'\'';"' \; >>lib/models/models.dart 12 | sed -i '' 's/enum_\:.*/enum_\: const \[\],/' $K8SMODELPATH/io_k8s_apiextensions_apiserver_pkg_apis_apiextensions_v1_json_schema_props.dart 13 | sed -i '' 's/Map> extra;/Map>? extra;/g' $K8SMODELPATH/io_*auth*.dart 14 | sed -i '' 's/Map> extra;/Map>? extra;/g' $K8SMODELPATH/io_*cert*.dart 15 | sed -i '' 's/DateTime.mapFromJson.*/mapValueOfType>(json, r'\''disruptedPods'\'') ?? const {}\,/g' ${K8SMODELPATH}/io_k8s_api_policy_v1_pod_disruption_budget_status.dart 16 | -------------------------------------------------------------------------------- /hack/build_macos_dmg.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | APP_NAME="k8z" 4 | BASEDIR="./build/macos/" 5 | CREATE_DMG=$(which create-dmg) 6 | VOLUME_NAME="${APP_NAME} installer" 7 | BGIMGFILE="macos/installer-background.png" 8 | DMG_FILE_NAME="${BASEDIR}${APP_NAME}.dmg" 9 | SRC_APP_FILE_DIR="${BASEDIR}Build/Products/Release/${APP_NAME}.app" 10 | VOLICON_FILE="${SRC_APP_FILE_DIR}/Contents/Resources/AppIcon.icns" 11 | 12 | # Since create-dmg does not clobber, be sure to delete previous DMG 13 | [[ -f "${DMG_FILE_NAME}" ]] && rm "${DMG_FILE_NAME}" 14 | 15 | # Create the DMG 16 | $CREATE_DMG \ 17 | --volname "${VOLUME_NAME}" \ 18 | --volicon "${VOLICON_FILE}" \ 19 | --background "${BGIMGFILE}" \ 20 | --window-pos 200 120 \ 21 | --window-size 800 400 \ 22 | --icon-size 100 \ 23 | --icon "${APP_NAME}.app" 200 210 \ 24 | --hide-extension "${APP_NAME}.app" \ 25 | --app-drop-link 600 200 \ 26 | "${DMG_FILE_NAME}" \ 27 | "${SRC_APP_FILE_DIR}" 28 | 29 | ls -alh "${DMG_FILE_NAME}" 30 | shasum -a 256 "${DMG_FILE_NAME}" >"${DMG_FILE_NAME}.checksum" 31 | shasum --check "${DMG_FILE_NAME}.checksum" 32 | -------------------------------------------------------------------------------- /lib/theme/kung.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const double smallBorderRadius = 8.0; 4 | ThemeData themeData() { 5 | return ThemeData( 6 | floatingActionButtonTheme: const FloatingActionButtonThemeData( 7 | foregroundColor: Colors.white, 8 | ), 9 | primarySwatch: Colors.amber, 10 | appBarTheme: const AppBarTheme( 11 | iconTheme: IconThemeData(color: Colors.black), 12 | // color: Colors.white, 13 | titleTextStyle: TextStyle( 14 | color: Colors.black54, 15 | fontSize: 21, 16 | fontWeight: FontWeight.w500, 17 | ), 18 | ), 19 | scaffoldBackgroundColor: Colors.grey[200], 20 | cardTheme: CardThemeData( 21 | color: Colors.white, 22 | shape: RoundedRectangleBorder( 23 | borderRadius: BorderRadius.circular(smallBorderRadius), 24 | ), 25 | ), 26 | buttonTheme: ButtonThemeData( 27 | textTheme: ButtonTextTheme.accent, 28 | shape: RoundedRectangleBorder( 29 | borderRadius: BorderRadius.circular(smallBorderRadius), 30 | ), 31 | ), 32 | fontFamily: 'Quicksand', 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /lib/providers/revenuecat_customer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:purchases_flutter/purchases_flutter.dart'; 3 | 4 | class RevenueCatCustomer with ChangeNotifier { 5 | CustomerInfo? _customerInfo; 6 | 7 | CustomerInfo? get customerInfo => _customerInfo; 8 | 9 | bool get isPurchased => () { 10 | bool result = false; 11 | var activeEntitlements = customerInfo?.entitlements.active ?? {}; 12 | 13 | for (var element in activeEntitlements.entries) { 14 | result = element.value.isActive; 15 | if (result) { 16 | break; 17 | } 18 | } 19 | return result; 20 | }(); 21 | 22 | init() async { 23 | try { 24 | _customerInfo = await Purchases.getCustomerInfo(); 25 | } catch (e) { 26 | rethrow; 27 | } 28 | } 29 | 30 | Future fetchCusterInfo() async { 31 | try { 32 | _customerInfo = await Purchases.getCustomerInfo(); 33 | notifyListeners(); 34 | } catch (e) { 35 | rethrow; 36 | } 37 | } 38 | 39 | void updateCustomerInfo(CustomerInfo? info) { 40 | _customerInfo = info; 41 | notifyListeners(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/dao/dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:k8zdev/common/ops.dart'; 2 | import 'package:k8zdev/dao/kube.dart'; 3 | import 'package:path/path.dart'; 4 | import 'package:sqflite/sqflite.dart'; 5 | 6 | late Database database; 7 | 8 | Future dbpath() async { 9 | var databasesPath = await getDatabasesPath(); 10 | return join(databasesPath, 'k8z.db'); 11 | } 12 | 13 | Future initStore() async { 14 | database = await openDatabase( 15 | await dbpath(), 16 | onCreate: (db, version) async { 17 | talker.log("onCreate, version: $version"); 18 | var batch = db.batch(); 19 | batch.execute(sqlCreateKubeClustersTable); 20 | 21 | await batch.commit(); 22 | }, 23 | onUpgrade: (db, oldVersion, newVersion) { 24 | talker.log("onUpgrade, old=$oldVersion, new: $newVersion"); 25 | }, 26 | // Set the version. This executes the onCreate function and provides a 27 | // path to perform database upgrades and downgrades. 28 | version: 1, 29 | ); 30 | } 31 | 32 | Future flushdb() async { 33 | var oldpath = await dbpath(); 34 | try { 35 | await deleteDatabase(oldpath); 36 | await initStore(); 37 | } on Exception { 38 | rethrow; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "41456452f29d64e8deb623a3c927524bcf9f111b" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 17 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 18 | - platform: ios 19 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 20 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 21 | - platform: macos 22 | create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 23 | base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /lib/widgets/set_api_timeout.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:k8zdev/generated/l10n.dart'; 3 | import 'package:k8zdev/providers/timeout.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | class ApiTimeoutWidget extends StatelessWidget { 7 | const ApiTimeoutWidget({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | var lang = S.current; 12 | var timeout = Provider.of(context, listen: true); 13 | return Container( 14 | height: 30, 15 | padding: const EdgeInsets.all(10), 16 | child: Column( 17 | children: [ 18 | Row( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | children: [ 21 | Text(lang.api_timeout), 22 | Text(lang.n_seconds(timeout.timeout)), 23 | ], 24 | ), 25 | Slider( 26 | value: timeout.timeout.toDouble(), 27 | onChanged: (double value) { 28 | timeout.update(value.toInt()); 29 | }, 30 | min: 1, 31 | max: 600, 32 | divisions: 120, 33 | label: lang.n_seconds(timeout.timeout), 34 | ), 35 | ], 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | 47 | # Go mobile builds 48 | /ios/Runner/libs/K8z.xcframework 49 | /android/app/src/libs 50 | 51 | tmp/ 52 | ios/libs/libk8z.a 53 | ios/libs/libk8z.arm64.a 54 | ios/libs/libk8z.x64.a 55 | /*.dart 56 | 57 | # secrets 58 | lib/firebase_options.dart 59 | ios/firebase_app_id_file.json 60 | ios/Runner/GoogleService-Info.plist 61 | macos/firebase_app_id_file.json 62 | lib/common/secrets.dart 63 | .qodo 64 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | ITSAppUsesNonExemptEncryption 24 | 25 | LSApplicationCategoryType 26 | public.app-category.developer-tools 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | NSHumanReadableCopyright 30 | $(PRODUCT_COPYRIGHT) 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | NSApplication 35 | 36 | 37 | -------------------------------------------------------------------------------- /lib/common/styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const defaultEdge = EdgeInsets.fromLTRB(13, 6, 13, 6); 4 | const Color navLightColor = Colors.white; 5 | final Color navDarkColor = Colors.grey.shade900; 6 | 7 | const smallTextStyle = TextStyle(fontSize: 12); 8 | 9 | const paywallEdge = EdgeInsets.fromLTRB(16, 32, 16, 16); 10 | const iapTosEdge = EdgeInsets.all(16); 11 | 12 | // UI Colors 13 | const kColorBar = Colors.pinkAccent; 14 | const kColorText = Colors.black54; 15 | const kPlanColorText = Colors.white; 16 | const kColorBackground = Colors.white; 17 | 18 | // Text Styles 19 | const kFontSizeSuperSmall = 10.0; 20 | const kFontSizeNormal = 16.0; 21 | const kFontSizeMedium = 18.0; 22 | const kFontSizeLarge = 96.0; 23 | 24 | const kDescriptionTextStyle = TextStyle( 25 | color: kColorText, 26 | fontWeight: FontWeight.normal, 27 | fontSize: kFontSizeNormal, 28 | ); 29 | 30 | const kTitleTextStyle = TextStyle( 31 | color: kPlanColorText, 32 | fontWeight: FontWeight.bold, 33 | fontSize: kFontSizeMedium, 34 | ); 35 | 36 | const purchaseExtraStyle = TextStyle( 37 | fontSize: 12, 38 | color: Colors.grey, 39 | fontWeight: FontWeight.normal, 40 | ); 41 | 42 | String getMonospaceFontFamily() { 43 | return 'DroidSansMNerdFontMono'; 44 | } 45 | 46 | final tileValueStyle = TextStyle( 47 | color: Colors.grey.shade500, 48 | fontWeight: FontWeight.normal, 49 | ); 50 | -------------------------------------------------------------------------------- /lib/widgets/context_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:k8zdev/providers/lang.dart'; 4 | import 'package:k8zdev/providers/current_cluster.dart'; 5 | 6 | /// 上下文提供器 Widget 7 | /// 负责为各种 Provider 设置当前的 BuildContext,以便它们能够处理标题更新 8 | class ContextProvider extends StatefulWidget { 9 | final Widget child; 10 | 11 | const ContextProvider({ 12 | super.key, 13 | required this.child, 14 | }); 15 | 16 | @override 17 | State createState() => _ContextProviderState(); 18 | } 19 | 20 | class _ContextProviderState extends State { 21 | @override 22 | void didChangeDependencies() { 23 | super.didChangeDependencies(); 24 | 25 | // 为各种 Provider 设置当前上下文 26 | _updateProviderContexts(); 27 | } 28 | 29 | void _updateProviderContexts() { 30 | try { 31 | // 为语言 Provider 设置上下文 32 | final localeProvider = Provider.of(context, listen: false); 33 | localeProvider.setCurrentContext(context); 34 | 35 | // 为集群 Provider 设置上下文 36 | final clusterProvider = Provider.of(context, listen: false); 37 | clusterProvider.setCurrentContext(context); 38 | } catch (e) { 39 | debugPrint('ContextProvider: Error setting provider contexts - $e'); 40 | } 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return widget.child; 46 | } 47 | } -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "app_icon_16.png", 5 | "idiom": "mac", 6 | "scale": "1x", 7 | "size": "16x16" 8 | }, 9 | { 10 | "filename": "app_icon_32.png", 11 | "idiom": "mac", 12 | "scale": "2x", 13 | "size": "16x16" 14 | }, 15 | { 16 | "filename": "app_icon_32.png", 17 | "idiom": "mac", 18 | "scale": "1x", 19 | "size": "32x32" 20 | }, 21 | { 22 | "filename": "app_icon_64.png", 23 | "idiom": "mac", 24 | "scale": "2x", 25 | "size": "32x32" 26 | }, 27 | { 28 | "filename": "app_icon_128.png", 29 | "idiom": "mac", 30 | "scale": "1x", 31 | "size": "128x128" 32 | }, 33 | { 34 | "filename": "app_icon_256.png", 35 | "idiom": "mac", 36 | "scale": "2x", 37 | "size": "128x128" 38 | }, 39 | { 40 | "filename": "app_icon_256.png", 41 | "idiom": "mac", 42 | "scale": "1x", 43 | "size": "256x256" 44 | }, 45 | { 46 | "filename": "app_icon_512.png", 47 | "idiom": "mac", 48 | "scale": "2x", 49 | "size": "256x256" 50 | }, 51 | { 52 | "filename": "app_icon_512.png", 53 | "idiom": "mac", 54 | "scale": "1x", 55 | "size": "512x512" 56 | }, 57 | { 58 | "filename": "app_icon_1024.png", 59 | "idiom": "mac", 60 | "scale": "2x", 61 | "size": "512x512" 62 | } 63 | ], 64 | "info": { 65 | "author": "icons_launcher", 66 | "version": 1 67 | } 68 | } -------------------------------------------------------------------------------- /lib/widgets/native_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'dart:io'; 4 | 5 | class ShowDialogToDismiss extends StatelessWidget { 6 | final String content; 7 | final String title; 8 | final String buttonText; 9 | 10 | const ShowDialogToDismiss( 11 | {super.key, 12 | required this.title, 13 | required this.buttonText, 14 | required this.content}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | if (!Platform.isIOS) { 19 | return AlertDialog( 20 | title: Text( 21 | title, 22 | ), 23 | content: Text( 24 | content, 25 | ), 26 | actions: [ 27 | TextButton( 28 | child: Text( 29 | buttonText, 30 | ), 31 | onPressed: () { 32 | Navigator.of(context).pop(); 33 | }, 34 | ), 35 | ], 36 | ); 37 | } else { 38 | return CupertinoAlertDialog( 39 | title: Text( 40 | title, 41 | ), 42 | content: Text( 43 | content, 44 | ), 45 | actions: [ 46 | CupertinoDialogAction( 47 | isDefaultAction: true, 48 | child: Text( 49 | buttonText[0].toUpperCase() + 50 | buttonText.substring(1).toLowerCase(), 51 | ), 52 | onPressed: () { 53 | Navigator.of(context).pop(); 54 | }, 55 | ) 56 | ]); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/providers/lang.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:intl/locale.dart' as intl; 4 | import 'package:k8zdev/services/stash.dart'; 5 | import 'package:k8zdev/services/title_update_service.dart'; 6 | 7 | const localeKey = "app_settings_locale_code"; 8 | 9 | class CurrentLocale with ChangeNotifier { 10 | late Locale? _locale; 11 | Locale? get locale => _locale; 12 | bool get isAuto => (_locale?.languageCode == null); 13 | 14 | String get languageCode => 15 | _locale?.languageCode ?? 16 | (intl.Locale.tryParse(Platform.localeName)?.languageCode ?? "en"); 17 | 18 | // 保存当前的 BuildContext 用于标题更新 19 | BuildContext? _currentContext; 20 | 21 | init() async { 22 | String? code = await vget(localeKey); 23 | _locale = (code != null) ? Locale(code) : null; 24 | } 25 | 26 | /// 设置当前的 BuildContext(用于标题更新) 27 | void setCurrentContext(BuildContext? context) { 28 | _currentContext = context; 29 | } 30 | 31 | void setLocale(Locale? locale) { 32 | final oldLanguage = languageCode; 33 | final newLanguage = locale?.languageCode ?? 34 | (intl.Locale.tryParse(Platform.localeName)?.languageCode ?? "en"); 35 | 36 | _locale = locale; 37 | vset(localeKey, locale?.languageCode); 38 | 39 | // 如果有当前上下文,处理语言切换的标题更新 40 | if (_currentContext != null && _currentContext!.mounted) { 41 | TitleUpdateService.handleLanguageChange( 42 | context: _currentContext!, 43 | newLanguage: newLanguage, 44 | oldLanguage: oldLanguage != newLanguage ? oldLanguage : null, 45 | ); 46 | } 47 | 48 | notifyListeners(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/widgets/settings_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_slidable/flutter_slidable.dart'; 3 | import 'package:go_router/go_router.dart'; 4 | import 'package:kubeconfig/kubeconfig.dart'; 5 | import 'package:settings_ui/settings_ui.dart'; 6 | 7 | AbstractSettingsTile metadataSettingsTile( 8 | BuildContext context, 9 | Widget child, 10 | String itemName, 11 | String? itemNamespace, 12 | String path, 13 | String resource, { 14 | void Function()? onTap, 15 | void Function()? onDoubleTap, 16 | ActionPane? startActionPane, 17 | ActionPane? endActionPane, 18 | }) { 19 | final slide = startActionPane != null || endActionPane != null; 20 | final gd = GestureDetector( 21 | onDoubleTap: onDoubleTap, 22 | onTap: onTap ?? 23 | () { 24 | GoRouter.of(context).pushNamed( 25 | "details", 26 | pathParameters: { 27 | "path": path, 28 | // empty namespace path param will case route miss, 29 | // we set default _ and remove it at the route. 30 | "namespace": itemNamespace.isNullOrEmpty ? "_" : itemNamespace!, 31 | "resource": resource, 32 | "name": itemName, 33 | }, 34 | ); 35 | }, 36 | child: AbsorbPointer(child: child), 37 | ); 38 | Widget internalChild; 39 | if (slide) { 40 | internalChild = Slidable( 41 | endActionPane: endActionPane, 42 | startActionPane: startActionPane, 43 | child: gd, 44 | ); 45 | } else { 46 | internalChild = gd; 47 | } 48 | 49 | return CustomSettingsTile( 50 | child: internalChild, 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/pages/k8s_list/cluster/create.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:go_router/go_router.dart'; 3 | import 'package:k8zdev/common/styles.dart'; 4 | import 'package:k8zdev/generated/l10n.dart'; 5 | 6 | class ClusterCreatePage extends StatefulWidget { 7 | final bool hiddenAppBar; 8 | const ClusterCreatePage({super.key, this.hiddenAppBar = false}); 9 | 10 | @override 11 | State createState() => _ClusterCreatePageState(); 12 | } 13 | 14 | class _ClusterCreatePageState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | var lang = S.of(context); 18 | AppBar? appBar; 19 | if (!widget.hiddenAppBar) { 20 | appBar = AppBar(title: Text(lang.add_cluster)); 21 | } 22 | 23 | return Scaffold( 24 | appBar: appBar, 25 | body: Container( 26 | padding: defaultEdge, 27 | child: Column( 28 | children: [ 29 | SizedBox( 30 | height: 300, 31 | child: ListView( 32 | children: ListTile.divideTiles( 33 | color: Colors.black12, 34 | tiles: [ 35 | ListTile( 36 | leading: const Icon(Icons.file_copy, size: 32), 37 | title: Center(child: Text(lang.manual_load_kubeconfig)), 38 | titleAlignment: ListTileTitleAlignment.center, 39 | onTap: () { 40 | GoRouter.of(context).pushNamed("manual_load"); 41 | }, 42 | ), 43 | ], 44 | ).toList(), 45 | ), 46 | ), 47 | ], 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.kiro/specs/google-analytics-page-title-fix/requirements.md: -------------------------------------------------------------------------------- 1 | # Google Analytics 页面标题修复需求文档 2 | 3 | ## 介绍 4 | 5 | 当前 Google Analytics 埋点存在页面标题上报问题,大量事件显示为 "(not set)",导致无法准确追踪用户的页面访问行为。通过代码分析发现,当前的 `logScreenView` 实现只传递了 `screenName` 参数,但没有正确设置页面标题信息,导致 Google Analytics 无法获取到有意义的页面标题数据。 6 | 7 | ## 需求 8 | 9 | ### 需求 1: 页面标题数据收集 10 | 11 | **用户故事:** 作为产品分析师,我希望在 Google Analytics 中能看到准确的页面标题信息,以便分析用户的页面访问行为和路径。 12 | 13 | #### 验收标准 14 | 15 | 1. WHEN 用户访问任何页面 THEN 系统应该收集并上报准确的页面标题到 Google Analytics 16 | 2. WHEN 页面标题发生变化 THEN 系统应该自动更新 Analytics 中的页面标题信息 17 | 3. WHEN 页面没有明确标题 THEN 系统应该使用合理的默认标题而不是 "(not set)" 18 | 19 | ### 需求 2: 多语言页面标题支持 20 | 21 | **用户故事:** 作为国际化应用的用户,我希望页面标题能够根据我的语言设置正确显示,以便在 Analytics 中按语言进行数据分析。 22 | 23 | #### 验收标准 24 | 25 | 1. WHEN 用户切换语言 THEN 页面标题应该使用对应语言的文本 26 | 2. WHEN 系统上报页面标题到 Analytics THEN 应该包含当前语言标识符 27 | 3. IF 某个页面标题没有对应语言翻译 THEN 系统应该使用英文作为默认标题 28 | 29 | ### 需求 3: 页面层级和上下文信息 30 | 31 | **用户故事:** 作为数据分析师,我希望页面标题能够包含页面层级和上下文信息,以便更好地理解用户的导航路径。 32 | 33 | #### 验收标准 34 | 35 | 1. WHEN 用户访问集群相关页面 THEN 页面标题应该包含当前集群名称 36 | 2. WHEN 用户访问资源详情页面 THEN 页面标题应该包含资源类型和名称 37 | 3. WHEN 用户访问命名空间相关页面 THEN 页面标题应该包含命名空间信息 38 | 4. WHEN 页面标题过长 THEN 系统应该合理截断但保留关键信息 39 | 40 | ### 需求 4: Analytics 事件增强 41 | 42 | **用户故事:** 作为开发者,我希望 Analytics 事件能够包含更丰富的上下文信息,以便进行更精确的用户行为分析。 43 | 44 | #### 验收标准 45 | 46 | 1. WHEN 系统上报页面访问事件 THEN 应该包含页面标题、路由路径、用户语言等信息 47 | 2. WHEN 用户在特定集群上下文中操作 THEN 事件应该包含集群标识信息 48 | 3. WHEN 发生页面导航 THEN 系统应该记录来源页面和目标页面信息 49 | 4. IF Analytics 上报失败 THEN 系统应该记录错误日志但不影响用户体验 50 | 51 | ### 需求 5: 页面标题一致性 52 | 53 | **用户故事:** 作为用户体验设计师,我希望应用中显示的页面标题与 Analytics 中记录的标题保持一致,以确保数据的准确性。 54 | 55 | #### 验收标准 56 | 57 | 1. WHEN 页面 AppBar 显示标题 THEN Analytics 中记录的标题应该与之一致 58 | 2. WHEN 页面标题动态变化 THEN Analytics 应该实时更新标题信息 59 | 3. WHEN 页面使用自定义标题格式 THEN Analytics 标题应该遵循相同的格式规范 60 | 4. WHEN 系统生成默认标题 THEN 应该使用统一的命名规范 -------------------------------------------------------------------------------- /gopkg/local/logsream.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "bufio" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/gorilla/websocket" 10 | corev1 "k8s.io/api/core/v1" 11 | ) 12 | 13 | func logstream(ctx *gin.Context) { 14 | var wsconn, _, clientset = ws(ctx) 15 | defer wsconn.Close() 16 | 17 | var name = ctx.Query("name") 18 | var namespace = ctx.Query("namespace") 19 | var container = ctx.Query("container") 20 | var tail, _ = strconv.ParseInt(ctx.Query("tail"), 10, 64) 21 | var since, _ = strconv.ParseInt(ctx.Query("since"), 10, 64) 22 | if tail == 0 { 23 | tail = 100 24 | } 25 | if since == 0 { 26 | since = 300 27 | } 28 | 29 | options := &corev1.PodLogOptions{ 30 | Follow: true, 31 | TailLines: &tail, 32 | SinceSeconds: &since, 33 | Container: container, 34 | } 35 | 36 | stream, err := clientset.CoreV1().Pods(namespace).GetLogs(name, options).Stream(ctx) 37 | if err != nil { 38 | wsconn.WriteMessage(websocket.TextMessage, []byte("could not stream logs: "+err.Error())) 39 | return 40 | } 41 | 42 | defer stream.Close() 43 | 44 | reader := bufio.NewReaderSize(stream, 16) 45 | lastLine := "" 46 | 47 | for { 48 | data, isPrefix, err := reader.ReadLine() 49 | if err != nil { 50 | wsconn.WriteMessage(websocket.TextMessage, []byte("read logs line failed: "+err.Error())) 51 | return 52 | } 53 | 54 | lines := strings.Split(string(data), "\r") 55 | length := len(lines) 56 | 57 | if len(lastLine) > 0 { 58 | lines[0] = lastLine + lines[0] 59 | lastLine = "" 60 | } 61 | 62 | if isPrefix { 63 | lastLine = lines[length-1] 64 | lines = lines[:(length - 1)] 65 | } 66 | 67 | for _, line := range lines { 68 | if err := wsconn.WriteMessage(websocket.TextMessage, []byte(line)); err != nil { 69 | return 70 | } 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | unnecessary_this: false 27 | use_string_in_part_of_directives: false 28 | avoid_function_literals_in_foreach_calls: false 29 | 30 | # Additional information about this file can be found at 31 | # https://dart.dev/guides/language/analysis-options 32 | 33 | analyzer: 34 | exclude: 35 | - tmp/** 36 | - native.dart 37 | - main.dart 38 | errors: 39 | deprecated_member_use: ignore 40 | unintended_html_in_doc_comment: ignore # Ignore HTML in doc comments (common in auto-generated Kubernetes models) 41 | -------------------------------------------------------------------------------- /gopkg/k8z/client.go: -------------------------------------------------------------------------------- 1 | package k8z 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | "strings" 8 | "time" 9 | 10 | "k8s.io/client-go/kubernetes" 11 | "k8s.io/client-go/rest" 12 | "k8s.io/client-go/tools/clientcmd" 13 | ) 14 | 15 | func NewCError(err error) string { 16 | return fmt.Sprintf("{\"error\": \"%s\"}", strings.ReplaceAll(err.Error(), "\"", "'")) 17 | } 18 | 19 | func NewClient(contextName, server, ca string, insecure bool, clientCert, clientKey, token, username, password, proxy string, timeout int64) (*rest.Config, *kubernetes.Clientset, error) { 20 | 21 | var bytes = []byte(`kind: Config 22 | apiVersion: v1 23 | clusters: 24 | - cluster: 25 | insecure-skip-tls-verify: ` + fmt.Sprintf("%v", insecure) + ` 26 | certificate-authority-data: ` + ca + ` 27 | server: ` + server + ` 28 | name: k8z-cluster 29 | contexts: 30 | - context: 31 | cluster: k8z-cluster 32 | user: k8z-user 33 | name: k8zctx 34 | current-context: k8zctx 35 | preferences: {} 36 | users: 37 | - name: k8z-user 38 | user: 39 | client-certificate-data: ` + clientCert + ` 40 | client-key-data: ` + clientKey + ` 41 | username: ` + username + ` 42 | password: ` + password + ` 43 | token: ` + token + ` 44 | `) 45 | 46 | config, err := clientcmd.NewClientConfigFromBytes(bytes) 47 | 48 | if err != nil { 49 | return nil, nil, err 50 | } 51 | 52 | restClient, err := config.ClientConfig() 53 | if err != nil { 54 | return nil, nil, err 55 | } 56 | 57 | if timeout > 0 { 58 | restClient.Timeout = time.Duration(timeout) * time.Second 59 | } 60 | 61 | if proxy != "" { 62 | proxyURL, err := url.Parse(proxy) 63 | if err != nil { 64 | return nil, nil, err 65 | } 66 | 67 | restClient.Transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)} 68 | } 69 | 70 | clientset, err := kubernetes.NewForConfig(restClient) 71 | if err != nil { 72 | return nil, nil, err 73 | } 74 | 75 | return restClient, clientset, nil 76 | } 77 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import connectivity_plus 9 | import file_selector_macos 10 | import firebase_analytics 11 | import firebase_core 12 | import firebase_crashlytics 13 | import in_app_review 14 | import package_info_plus 15 | import path_provider_foundation 16 | import purchases_flutter 17 | import screen_retriever_macos 18 | import share_plus 19 | import sqflite_darwin 20 | import url_launcher_macos 21 | import window_manager 22 | 23 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 24 | ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) 25 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 26 | FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin")) 27 | FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) 28 | FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin")) 29 | InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) 30 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 31 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 32 | PurchasesFlutterPlugin.register(with: registry.registrar(forPlugin: "PurchasesFlutterPlugin")) 33 | ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) 34 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 35 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 36 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 37 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) 38 | } 39 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '13.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | pod 'Firebase/Analytics' 33 | use_modular_headers! 34 | 35 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 36 | target 'RunnerTests' do 37 | inherit! :search_paths 38 | end 39 | end 40 | 41 | post_install do |installer| 42 | installer.pods_project.targets.each do |target| 43 | flutter_additional_ios_build_settings(target) 44 | 45 | # Fix for RevenueCat SubscriptionPeriod conflict with iOS 17+ StoreKit 46 | if target.name == 'PurchasesHybridCommon' 47 | target.build_configurations.each do |config| 48 | config.build_settings['SWIFT_ACTIVE_COMPILATION_CONDITIONS'] ||= '' 49 | config.build_settings['SWIFT_ACTIVE_COMPILATION_CONDITIONS'] << ' PURCHASES_HYBRID_COMMON' 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | K8Z 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | k8z 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | LSSupportsOpeningDocumentsInPlace 28 | 29 | UIFileSharingEnabled 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | CADisableMinimumFrameDurationOnPhone 49 | 50 | UIApplicationSupportsIndirectInputEvents 51 | 52 | ITSAppUsesNonExemptEncryption 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '12.3' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | target.build_configurations.each do |config| 43 | config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '12.3' 44 | # config.build_settings['SWIFT_VERSION'] = '5.0' 45 | if config.base_configuration_reference.is_a? Xcodeproj::Project::Object::PBXFileReference 46 | xcconfig_path = config.base_configuration_reference.real_path 47 | IO.write(xcconfig_path, IO.read(xcconfig_path).gsub("DT_TOOLCHAIN_DIR", "TOOLCHAIN_DIR")) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/pages/settings/locale.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:k8zdev/common/ops.dart'; 3 | import 'package:k8zdev/generated/l10n.dart'; 4 | import 'package:k8zdev/providers/lang.dart'; 5 | import 'package:k8zdev/widgets/widgets.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | class LocaleSettingPage extends StatefulWidget { 9 | const LocaleSettingPage({super.key}); 10 | 11 | @override 12 | State createState() => _LocaleSettingPageState(); 13 | } 14 | 15 | class _LocaleSettingPageState extends State { 16 | void _changed(String? value, CurrentLocale current) { 17 | Locale? locale; 18 | talker.log("change, value is null: ${value == null}"); 19 | if (value == null) { 20 | current.setLocale(locale); 21 | return; 22 | } 23 | 24 | locale = Locale(value); 25 | current.setLocale(locale); 26 | } 27 | 28 | Widget body(BuildContext context, S lang) { 29 | var current = Provider.of(context, listen: true); 30 | String? code = current.locale?.languageCode; 31 | 32 | return Column( 33 | children: [ 34 | RadioListTile( 35 | title: Text(lang.general_language_null), 36 | value: null, 37 | groupValue: code, 38 | onChanged: (v) => _changed(v, current), 39 | selected: current.isAuto, 40 | ), 41 | RadioListTile( 42 | title: Text(lang.general_language_zh), 43 | value: 'zh', 44 | groupValue: code, 45 | selected: code == "zh", 46 | onChanged: (v) => _changed(v, current), 47 | ), 48 | RadioListTile( 49 | title: Text(lang.general_language_en), 50 | value: 'en', 51 | groupValue: code, 52 | selected: code == "en", 53 | onChanged: (v) => _changed(v, current), 54 | ), 55 | ], 56 | ); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | var lang = S.of(context); 62 | 63 | return safeSca( 64 | context, 65 | Text("${lang.general_language}/${lang.settings}"), 66 | body(context, lang), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/widgets/tiles.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:clipboard/clipboard.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_highlighting/flutter_highlighting.dart'; 6 | import 'package:flutter_highlighting/themes/github-dark-dimmed.dart'; 7 | import 'package:highlighting/languages/yaml.dart'; 8 | import 'package:k8zdev/common/styles.dart'; 9 | import 'package:k8zdev/services/k8z_native.dart'; 10 | import 'package:k8zdev/widgets/widgets.dart'; 11 | import 'package:settings_ui/settings_ui.dart'; 12 | 13 | SettingsTile copyTile(String name, {String? value}) { 14 | return SettingsTile( 15 | title: Text( 16 | name, 17 | style: tileValueStyle, 18 | ), 19 | trailing: IconButton( 20 | onPressed: () async { 21 | await FlutterClipboard.copy(value ?? name); 22 | }, 23 | icon: const Icon(Icons.copy), 24 | ), 25 | onPressed: null, 26 | ); 27 | } 28 | 29 | SettingsTile copyTileValue(String name, String value, String langCode, 30 | {double? zhLen, double? enLen}) { 31 | return SettingsTile( 32 | leading: leadingText(name, langCode, zhLen: zhLen, enLen: enLen), 33 | title: Text( 34 | value, 35 | style: tileValueStyle, 36 | ), 37 | trailing: IconButton( 38 | onPressed: () async { 39 | await FlutterClipboard.copy(value); 40 | }, 41 | icon: const Icon(Icons.copy), 42 | ), 43 | onPressed: null, 44 | ); 45 | } 46 | 47 | SettingsTile copyTileYaml(String name, dynamic value, String langCode, 48 | {double? zhLen, double? enLen}) { 49 | final yamlValue = K8zNative.json2yaml(jsonEncode(value)); 50 | 51 | return SettingsTile( 52 | leading: leadingText(name, langCode, enLen: enLen, zhLen: zhLen), 53 | title: HighlightView( 54 | yamlValue, 55 | languageId: yaml.id, 56 | theme: githubDarkDimmedTheme, 57 | padding: defaultEdge, 58 | textStyle: const TextStyle( 59 | fontSize: 16, 60 | ), 61 | ), 62 | trailing: IconButton( 63 | onPressed: () async { 64 | await FlutterClipboard.copy(yamlValue.toString()); 65 | }, 66 | icon: const Icon(Icons.copy), 67 | ), 68 | onPressed: null, 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # k8zdev 2 | [![docs](https://img.shields.io/badge/k8z.dev-docs-red)](https://k8z.dev/docs?_utm=github) | ![GitHub last commit](https://img.shields.io/github/last-commit/k8zdev/k8z) | ![GitHub commit activity](https://img.shields.io/github/commit-activity/t/k8zdev/k8z) | ![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/k8zdev/k8z) | ![K8Z GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/k8zdev/k8z/total) 3 | 4 | 5 | 6 | k8s admin app 7 | 8 | ## Getting Started 9 | 10 | You can directly install `k8z` from Apple App store. 11 | - iPhone: https://apps.apple.com/us/app/k8z/id6478047741?platform=iphone 12 | - iPad: https://apps.apple.com/us/app/k8z/id6478047741?platform=ipad 13 | 14 | 15 | # Roadmap 16 | 17 | ## Futures 18 | 19 | ### Basic futures 20 | 21 | - [x] App's appearance: light/dark mode. 22 | 23 | - [x] L18n supports: English, Chinese. 24 | 25 | ### Cluster management 26 | - [x] Cluster overview metric charts. 27 | 28 | - [x] Show added clusters list. 29 | 30 | - [x] Add clusters from kubeconfig file. 31 | 32 | - [x] Local persistent storage of cluster information. 33 | 34 | - [x] Delete cluster info from local storage. 35 | 36 | - [x] Show overview of cluster, like kubernetes version, running status, warnning events. 37 | 38 | - [ ] Add clusters from kubernetes cloud providers, like awk, gce etc. 39 | 40 | 41 | ### Resource management 42 | 43 | - [x] Supported resources page. 44 | - [x] Nodes list page. 45 | - [ ] Node's detail info page. 46 | - [x] Events list page. 47 | - [ ] Event's detail info page. 48 | - [x] Namespaces list page. 49 | - [ ] Namespace's detail info page. 50 | - [x] CRDs list page. 51 | - [ ] CRD's detail info page. 52 | - [x] ConfigMaps list page. 53 | - [ ] ConfigMap's detail info page. 54 | - [x] Secrets list page. 55 | - [ ] Secret's detail info page. 56 | - [x] SAs list page. 57 | - [ ] SA's detail info page. 58 | - [x] StorageClass list page. 59 | - [ ] StorageClass's detail info page. 60 | - [x] PVs list page. 61 | - [ ] PV's detail info page. 62 | - [x] PVCs list page. 63 | - [ ] PVC's detail info page. 64 | 65 | ### Workload management 66 | 67 | - [x] Supported workloads page. 68 | - [x] Pods list page. 69 | - [x] DaemonSets list page. 70 | - [x] Deployments list page. 71 | - [x] StatefulSets list page. 72 | - [x] Endpoints list page. 73 | - [x] Ingresses list page. 74 | - [x] Services list page. 75 | 76 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | K8SAPIVER=v1.25.0 2 | K8SMODELPATH=lib/models/kubernetes 3 | K8SAPISPEC=https://raw.githubusercontent.com/kubernetes/kubernetes/${K8SAPIVER}/api/openapi-spec/swagger.json 4 | 5 | .PHONY: bindings-android 6 | 7 | .PHONY: install-gomobile 8 | install-gomobile: 9 | go install golang.org/x/mobile/cmd/gomobile@latest 10 | 11 | .PHONY: bindings-ios 12 | bindings-ios: 13 | mkdir -p ios/Runner/libs 14 | gomobile bind -o ios/Runner/libs/K8z.xcframework -target=ios github.com/k8zdev/k8z/cmd/mobile 15 | 16 | .PHONY: library-ios 17 | library-ios: 18 | GOARCH=amd64 GOOS=darwin CGO_ENABLED=1 SDK=iphonesimulator CC=$(PWD)/hack/clangwrap.sh CGO_CFLAGS="-fembed-bitcode" go build -buildmode=c-archive -o tmp/libs/libk8z.x64.a github.com/k8zdev/k8z/cmd/ 19 | GOARCH=arm64 GOOS=ios CGO_ENABLED=1 SDK=iphoneos CC=$(PWD)/hack/clangwrap.sh CGO_CFLAGS="-fembed-bitcode" go build -buildmode=c-archive -o tmp/libs/libk8z.arm64.a github.com/k8zdev/k8z/cmd/ 20 | mkdir -p ios/libs/ && cp tmp/libs/libk8z.arm64.h ios/libs/libk8z.h 21 | lipo -create tmp/libs/libk8z.x64.a tmp/libs/libk8z.arm64.a -output ios/libs/libk8z.a 22 | 23 | .PHONY: library-macos 24 | library-macos: 25 | GOARCH=amd64 GOOS=darwin CGO_ENABLED=1 go build -ldflags "-w" -buildmode c-shared -o tmp/libs/libk8z.macos.x64.dylib github.com/k8zdev/k8z/cmd/ 26 | GOARCH=arm64 GOOS=darwin CGO_ENABLED=1 go build -ldflags "-w" -buildmode c-shared -o tmp/libs/libk8z.macos.arm64.dylib github.com/k8zdev/k8z/cmd/ 27 | lipo -create tmp/libs/libk8z.macos.x64.dylib tmp/libs/libk8z.macos.arm64.dylib -output macos/k8z.dylib 28 | 29 | .PHONY: library 30 | library: library-macos library-ios 31 | 32 | .PHONY: gen-k8s-api-model 33 | gen-k8s-api-model: 34 | @rm -fr tmp/lib/* && sed -i '' '/io_k8s.*$ /d' lib/models/models.dart 35 | @docker run --rm -v "${PWD}/tmp/:/local/" -v "${PWD}/hack/k8sapi/:/api/" openapitools/openapi-generator-cli:latest generate -i /api/${K8SAPIVER}_swagger.json -g dart -o /local/ 36 | @bash hack/codefix.sh 37 | 38 | .PHONY: update-icon 39 | update-icon: 40 | dart run icons_launcher:create 41 | 42 | .PHONY: decrypt 43 | decrypt: 44 | @gpg --quiet --batch --yes --decrypt --passphrase="${LARGE_SECRET_PASSPHRASE}" \ 45 | --output ${PWD}/lib/firebase_options.dart ${PWD}/lib/firebase_options.dart.gpg 46 | echo "Decrypt firebase_options done" 47 | @gpg --quiet --batch --yes --decrypt --passphrase="${LARGE_SECRET_PASSPHRASE}" \ 48 | --output ${PWD}/lib/common/secrets.dart ${PWD}/lib/common/secrets.dart.gpg 49 | echo "Decrypt secrets done" 50 | 51 | .PHONY: dmg 52 | dmg: 53 | @echo Building DMG... 54 | @hack/build_macos_dmg.sh 55 | -------------------------------------------------------------------------------- /lib/providers/current_cluster.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:k8zdev/dao/kube.dart'; 5 | import 'package:k8zdev/services/stash.dart'; 6 | import 'package:k8zdev/services/title_update_service.dart'; 7 | import 'package:kubeconfig/kubeconfig.dart'; 8 | 9 | const currentClusterKey = "app_default_cluster"; 10 | 11 | class CurrentCluster with ChangeNotifier { 12 | static K8zCluster? _current; 13 | static K8zCluster? get current { 14 | return _current; 15 | } 16 | 17 | K8zCluster? get cluster { 18 | return _current; 19 | } 20 | 21 | // 保存当前的 BuildContext 用于标题更新 22 | BuildContext? _currentContext; 23 | 24 | init() async { 25 | String? raw = await vget(currentClusterKey); 26 | if (raw.isNullOrEmpty || raw == null || raw == "null") { 27 | return; 28 | } 29 | final json = jsonDecode(raw); 30 | K8zCluster? current = K8zCluster.fromJson(json); 31 | _current = current; 32 | } 33 | 34 | /// 设置当前的 BuildContext(用于标题更新) 35 | void setCurrentContext(BuildContext? context) { 36 | _currentContext = context; 37 | } 38 | 39 | void setCurrent(K8zCluster? cluster) { 40 | final oldCluster = _current?.name; 41 | final newCluster = cluster?.name; 42 | 43 | CurrentCluster._current = cluster; 44 | vset(currentClusterKey, jsonEncode(_current?.toJson())); 45 | 46 | // 如果有当前上下文且集群发生了变化,处理集群切换的标题更新 47 | if (_currentContext != null && _currentContext!.mounted && oldCluster != newCluster) { 48 | TitleUpdateService.handleClusterChange( 49 | context: _currentContext!, 50 | newCluster: newCluster, 51 | oldCluster: oldCluster, 52 | ); 53 | } 54 | 55 | notifyListeners(); 56 | } 57 | 58 | void updateNamespace(String? namespace) { 59 | if (_current == null) { 60 | return; 61 | } 62 | 63 | final oldNamespace = _current!.namespace.isEmpty ? null : _current!.namespace; 64 | final newNamespace = namespace?.isEmpty == true ? null : namespace; 65 | 66 | _current!.namespace = namespace ?? ""; 67 | vset(currentClusterKey, jsonEncode(_current?.toJson())); 68 | K8zCluster.update(_current!); 69 | 70 | // 如果有当前上下文且命名空间发生了变化,处理命名空间切换的标题更新 71 | if (_currentContext != null && _currentContext!.mounted && oldNamespace != newNamespace) { 72 | TitleUpdateService.handleNamespaceChange( 73 | context: _currentContext!, 74 | newNamespace: newNamespace, 75 | oldNamespace: oldNamespace, 76 | ); 77 | } 78 | 79 | notifyListeners(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | // Ignore issues from commonly used lints in this file. 6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new 7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering 8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment 9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases 10 | // ignore_for_file:comment_references 11 | 12 | import 'dart:async'; 13 | 14 | import 'package:flutter/foundation.dart'; 15 | import 'package:intl/intl.dart'; 16 | import 'package:intl/message_lookup_by_library.dart'; 17 | import 'package:intl/src/intl_helpers.dart'; 18 | 19 | import 'messages_en.dart' as messages_en; 20 | import 'messages_zh_CN.dart' as messages_zh_cn; 21 | 22 | typedef Future LibraryLoader(); 23 | Map _deferredLibraries = { 24 | 'en': () => new SynchronousFuture(null), 25 | 'zh_CN': () => new SynchronousFuture(null), 26 | }; 27 | 28 | MessageLookupByLibrary? _findExact(String localeName) { 29 | switch (localeName) { 30 | case 'en': 31 | return messages_en.messages; 32 | case 'zh_CN': 33 | return messages_zh_cn.messages; 34 | default: 35 | return null; 36 | } 37 | } 38 | 39 | /// User programs should call this before using [localeName] for messages. 40 | Future initializeMessages(String localeName) { 41 | var availableLocale = Intl.verifiedLocale( 42 | localeName, 43 | (locale) => _deferredLibraries[locale] != null, 44 | onFailure: (_) => null, 45 | ); 46 | if (availableLocale == null) { 47 | return new SynchronousFuture(false); 48 | } 49 | var lib = _deferredLibraries[availableLocale]; 50 | lib == null ? new SynchronousFuture(false) : lib(); 51 | initializeInternalMessageLookup(() => new CompositeMessageLookup()); 52 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 53 | return new SynchronousFuture(true); 54 | } 55 | 56 | bool _messagesExistFor(String locale) { 57 | try { 58 | return _findExact(locale) != null; 59 | } catch (e) { 60 | return false; 61 | } 62 | } 63 | 64 | MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { 65 | var actualLocale = Intl.verifiedLocale( 66 | locale, 67 | _messagesExistFor, 68 | onFailure: (_) => null, 69 | ); 70 | if (actualLocale == null) return null; 71 | return _findExact(actualLocale); 72 | } 73 | -------------------------------------------------------------------------------- /lib/widgets/widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_hyphenating_text/auto_hyphenating_text.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:k8zdev/common/helpers.dart'; 4 | import 'package:k8zdev/common/styles.dart'; 5 | 6 | const Widget topSizeBox = SizedBox(height: 12); 7 | const Widget naviNextIcon = Icon(Icons.navigate_next); 8 | Widget divider200() => 9 | Divider(height: 0, indent: 0, color: Colors.grey.shade200); 10 | 11 | Widget safeSca( 12 | BuildContext context, 13 | Widget title, 14 | Widget child, { 15 | Widget? leading, 16 | List? actions, 17 | }) { 18 | return Scaffold( 19 | extendBody: true, 20 | appBar: AppBar( 21 | title: title, 22 | actions: actions, 23 | leading: leading, 24 | ), 25 | body: child, 26 | backgroundColor: context.isDarkMode ? navDarkColor : Colors.white, 27 | ); 28 | } 29 | 30 | const errorIcon = Icon( 31 | Icons.error_outline, 32 | color: Colors.red, 33 | ); 34 | 35 | const runningIcon = Icon( 36 | Icons.donut_large_rounded, 37 | color: Colors.green, 38 | ); 39 | 40 | const quizIcon = Icon( 41 | Icons.donut_large_rounded, 42 | color: Colors.amber, 43 | ); 44 | 45 | final buildingWidget = Center( 46 | child: TextButton.icon( 47 | onPressed: null, 48 | icon: const Icon(Icons.work_history_rounded), 49 | label: const Text("Building"), 50 | ), 51 | ); 52 | 53 | final emptyWidget = Center( 54 | child: TextButton.icon( 55 | onPressed: null, 56 | icon: const Icon(Icons.hourglass_empty), 57 | label: const Text("Empty"), 58 | ), 59 | ); 60 | 61 | const smallProgressIndicator = SizedBox( 62 | height: 16, 63 | width: 16, 64 | child: CircularProgressIndicator(), 65 | ); 66 | 67 | Widget leadingText(String label, String langCode, 68 | {double? zhLen = 32, double? enLen = 52}) { 69 | late double len; 70 | switch (langCode) { 71 | case "zh": 72 | len = zhLen ?? 32; 73 | default: 74 | len = enLen ?? 52; 75 | } 76 | return SizedBox( 77 | width: len, 78 | child: AutoHyphenatingText( 79 | label, 80 | ), 81 | ); 82 | } 83 | 84 | Widget maxWidthText(BuildContext context, String label, 85 | {double rate = 0.5, 86 | double maxWidth = double.infinity, 87 | TextStyle? style, 88 | bool hpyhenationg = false}) { 89 | final size = MediaQuery.of(context).size; 90 | var width = size.width * rate; 91 | style ??= tileValueStyle; 92 | if (maxWidth.isFinite && width > maxWidth) { 93 | width = maxWidth; 94 | } 95 | return SizedBox( 96 | width: width, 97 | child: hpyhenationg 98 | ? AutoHyphenatingText( 99 | label, 100 | style: style, 101 | ) 102 | : Text( 103 | label, 104 | style: style, 105 | ), 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /lib/widgets/detail_widgets/services.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:k8zdev/generated/l10n.dart'; 3 | import 'package:k8zdev/models/models.dart'; 4 | import 'package:k8zdev/widgets/tiles.dart'; 5 | import 'package:settings_ui/settings_ui.dart'; 6 | 7 | List buildServicesTiles( 8 | BuildContext context, 9 | IoK8sApiCoreV1ServiceSpec? spec, 10 | IoK8sApiCoreV1ServiceStatus? status, 11 | String langCode, 12 | ) { 13 | final lang = S.of(context); 14 | 15 | List tiles = []; 16 | 17 | if (spec == null) { 18 | return [SettingsTile(title: Text(lang.empty))]; 19 | } 20 | 21 | var externalIps = ""; 22 | if (spec.type == "LoadBalancer") { 23 | externalIps = status!.loadBalancer!.ingress 24 | .map((ingress) => "${ingress.ip}") 25 | .join(", "); 26 | } 27 | 28 | tiles.addAll([ 29 | copyTileValue( 30 | lang.type, 31 | spec.type ?? "", 32 | langCode, 33 | enLen: 72.0, 34 | zhLen: 72.0, 35 | ), 36 | copyTileValue( 37 | lang.cluster_ip, 38 | spec.clusterIP ?? "", 39 | langCode, 40 | enLen: 72.0, 41 | zhLen: 72.0, 42 | ), 43 | if (spec.externalName != null) 44 | copyTileValue( 45 | lang.external_name, 46 | spec.externalName ?? "", 47 | langCode, 48 | enLen: 72.0, 49 | zhLen: 72.0, 50 | ), 51 | if (spec.externalIPs.isNotEmpty || externalIps != "") 52 | copyTileValue( 53 | lang.external_ips, 54 | spec.externalIPs.isNotEmpty ? spec.externalIPs.join(", ") : externalIps, 55 | langCode, 56 | enLen: 72.0, 57 | zhLen: 72.0, 58 | ), 59 | if (spec.loadBalancerIP != null) 60 | copyTileValue( 61 | lang.load_balancer_ip, 62 | spec.loadBalancerIP ?? "", 63 | langCode, 64 | enLen: 72.0, 65 | zhLen: 72.0, 66 | ), 67 | copyTileValue( 68 | lang.ports, 69 | spec.ports 70 | .map((port) => 71 | "${port.port}${(port.nodePort != null) ? ":${port.nodePort}" : ""}/${port.protocol}") 72 | .join(", "), 73 | langCode, 74 | enLen: 72.0, 75 | zhLen: 72.0, 76 | ), 77 | copyTileYaml( 78 | lang.ports, 79 | spec.ports, 80 | langCode, 81 | enLen: 72.0, 82 | zhLen: 45.0, 83 | ), 84 | copyTileYaml( 85 | lang.selector, 86 | spec.selector, 87 | langCode, 88 | enLen: 72.0, 89 | zhLen: 45.0, 90 | ), 91 | if (spec.sessionAffinity != null) 92 | copyTileValue( 93 | lang.session_affinity, 94 | spec.sessionAffinity ?? "", 95 | langCode, 96 | enLen: 72.0, 97 | zhLen: 72.0, 98 | ), 99 | ]); 100 | 101 | return tiles; 102 | } 103 | -------------------------------------------------------------------------------- /lib/common/resources/pods.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:k8zdev/common/resources/resources.dart'; 3 | import 'package:k8zdev/models/models.dart'; 4 | import 'package:k8zdev/widgets/widgets.dart'; 5 | 6 | const podOkStatusList = ["Running", "Succeeded"]; 7 | 8 | class PodResources { 9 | String cpu; 10 | String memory; 11 | 12 | PodResources({required this.cpu, required this.memory}); 13 | } 14 | 15 | /// [getPodStatus] returns pod status string and icon from [pod]. 16 | (String status, Widget icon) getPodStatus(IoK8sApiCoreV1Pod? pod) { 17 | if (pod == null) { 18 | return ("-", quizIcon); 19 | } 20 | 21 | final phase = pod.status?.phase ?? 'Unknown'; 22 | var reason = pod.status?.reason ?? ''; 23 | 24 | for (var cs in pod.status!.containerStatuses) { 25 | if (cs.state?.waiting != null) { 26 | reason = cs.state?.waiting?.reason ?? ''; 27 | break; 28 | } 29 | 30 | if (cs.state?.terminated != null) { 31 | reason = cs.state?.terminated?.reason ?? ''; 32 | break; 33 | } 34 | } 35 | 36 | reason = (reason != '') ? reason : phase; 37 | return (reason, podOkStatusList.contains(reason) ? runningIcon : errorIcon); 38 | } 39 | 40 | /// [getRestarts] return restarts count number for a [pod]. 41 | /// The number is sum of all init containers and containers. 42 | int getRestarts(IoK8sApiCoreV1Pod? pod) { 43 | final cs = pod?.status?.containerStatuses; 44 | final count = cs != null && cs.isNotEmpty 45 | ? cs 46 | .map((status) => status.restartCount) 47 | .reduce((count1, count2) => count1 + count2) 48 | : 0; 49 | final ics = pod?.status?.initContainerStatuses; 50 | final initCount = ics != null && ics.isNotEmpty 51 | ? ics 52 | .map((status) => status.restartCount) 53 | .reduce((count1, count2) => count1 + count2) 54 | : 0; 55 | 56 | return count + initCount; 57 | } 58 | 59 | PodResources? getPodResources(IoK8sApiCoreV1Pod? pod) { 60 | if (pod == null || pod.spec == null || pod.spec!.containers.isEmpty) { 61 | return null; 62 | } 63 | 64 | int cpuLimits = 0; 65 | int memLimits = 0; 66 | int cpuRequests = 0; 67 | int memRequests = 0; 68 | 69 | for (var container in pod.spec!.containers) { 70 | var limits = container.resources!.limits; 71 | if (limits.containsKey('cpu')) { 72 | cpuLimits += parseCpuRes(limits['cpu']!); 73 | } 74 | if (limits.containsKey('memory')) { 75 | memLimits += parseMemRes(limits['memory']!); 76 | } 77 | 78 | var requests = container.resources!.requests; 79 | if (requests.containsKey('cpu')) { 80 | cpuRequests += parseCpuRes(requests['cpu']!); 81 | } 82 | if (requests.containsKey('memory')) { 83 | memRequests += parseMemRes(requests['memory']!); 84 | } 85 | } 86 | 87 | return PodResources( 88 | cpu: '${formatCpuRes(cpuRequests)} / ${formatCpuRes(cpuLimits)}', 89 | memory: '${formatMemRes(memRequests)} / ${formatMemRes(memLimits)}', 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /lib/common/resources/resources.dart: -------------------------------------------------------------------------------- 1 | const _cpuUnitMap = { 2 | 'n': 1, 3 | 'u': 1000, 4 | 'm': 1000 * 1000, 5 | 'k': 1000 * 1000 * 1000 * 1000, 6 | 'K': 1000 * 1000 * 1000 * 1000, 7 | 'ki': 1000 * 1000 * 1000 * 1000, 8 | 'Ki': 1000 * 1000 * 1000 * 1000, 9 | '': 1000 * 1000 * 1000, 10 | }; 11 | const _memUnitMap = { 12 | 'k': 1024, 13 | 'K': 1024, 14 | 'ki': 1024, 15 | 'Ki': 1024, 16 | 'm': 1024 * 1024, 17 | 'M': 1024 * 1024, 18 | 'mi': 1024 * 1024, 19 | 'Mi': 1024 * 1024, 20 | 'g': 1024 * 1024 * 1024, 21 | 'G': 1024 * 1024 * 1024, 22 | 'gi': 1024 * 1024 * 1024, 23 | 'Gi': 1024 * 1024 * 1024, 24 | 't': 1024 * 1024 * 1024 * 1024, 25 | 'T': 1024 * 1024 * 1024 * 1024, 26 | 'ti': 1024 * 1024 * 1024 * 1024, 27 | 'Ti': 1024 * 1024 * 1024 * 1024, 28 | }; 29 | 30 | int _parseRes(String resType, String raw, Map maps) { 31 | if (raw == '') { 32 | return 0; 33 | } 34 | 35 | for (var map in maps.entries) { 36 | if (raw.endsWith(map.key)) { 37 | var num = raw.substring(0, raw.length - map.key.length); 38 | return double.parse(num).toInt() * map.value; 39 | } 40 | } 41 | return double.parse(raw).toInt(); 42 | } 43 | 44 | /// [parseCpuRes] converts cpu [raw] resource [String] to [int]. 45 | /// int(1) means the smallest unit for CPU `n`. 46 | /// k8s doc: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#resource-units-in-kubernetes 47 | /// These CPU resources are managed by the kube-scheduler using the Linux CFS: https://en.wikipedia.org/wiki/Completely_Fair_Scheduler 48 | /// 49 | /// 50 | int parseCpuRes(String raw) { 51 | return _parseRes('cpu', raw, _cpuUnitMap); 52 | } 53 | 54 | /// [parseMemRes] converts mem [raw] resource [String] to [int]. 55 | /// int(1) means the smallest unit for Mem `byte`. 56 | /// 57 | /// > Pay attention to the case of the suffixes. If you request 400m of memory, this is a request for 0.4 bytes. Someone who types that probably meant to ask for 400 mebibytes (400Mi) or 400 megabytes (400M). 58 | int parseMemRes(String raw) { 59 | return _parseRes('mem', raw, _memUnitMap); 60 | } 61 | 62 | String formatCpuRes(int raw, {round = 2}) { 63 | return (raw / 1e9.toInt()).toStringAsFixed(round); 64 | } 65 | 66 | const _memUnit = 1024; 67 | String formatMemRes(int raw, {round = 2}) { 68 | if (raw < _memUnit) { 69 | return '$raw'; 70 | } 71 | 72 | if (raw < _memUnit * _memUnit && raw % _memUnit == 0) { 73 | return '${(raw / _memUnit).toStringAsFixed(round)}Ki'; 74 | } 75 | 76 | if (raw < _memUnit * _memUnit) { 77 | return '${(raw / _memUnit).toStringAsFixed(round)}Ki'; 78 | } 79 | 80 | if (raw < _memUnit * _memUnit * _memUnit && raw % _memUnit == 0) { 81 | return '${(raw / (_memUnit * _memUnit)).toStringAsFixed(round)}Mi'; 82 | } 83 | 84 | if (raw < _memUnit * _memUnit * _memUnit * _memUnit && raw % _memUnit == 0) { 85 | return '${(raw / (_memUnit * _memUnit * _memUnit)).toStringAsFixed(round)}Gi'; 86 | } 87 | 88 | return '${(raw / _memUnit / _memUnit / _memUnit / _memUnit).toStringAsFixed(round)}Ti'; 89 | } 90 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "Icon-App-20x20@2x.png", 5 | "idiom": "iphone", 6 | "scale": "2x", 7 | "size": "20x20" 8 | }, 9 | { 10 | "filename": "Icon-App-20x20@3x.png", 11 | "idiom": "iphone", 12 | "scale": "3x", 13 | "size": "20x20" 14 | }, 15 | { 16 | "filename": "Icon-App-29x29@1x.png", 17 | "idiom": "iphone", 18 | "scale": "1x", 19 | "size": "29x29" 20 | }, 21 | { 22 | "filename": "Icon-App-29x29@2x.png", 23 | "idiom": "iphone", 24 | "scale": "2x", 25 | "size": "29x29" 26 | }, 27 | { 28 | "filename": "Icon-App-29x29@3x.png", 29 | "idiom": "iphone", 30 | "scale": "3x", 31 | "size": "29x29" 32 | }, 33 | { 34 | "filename": "Icon-App-40x40@2x.png", 35 | "idiom": "iphone", 36 | "scale": "2x", 37 | "size": "40x40" 38 | }, 39 | { 40 | "filename": "Icon-App-40x40@3x.png", 41 | "idiom": "iphone", 42 | "scale": "3x", 43 | "size": "40x40" 44 | }, 45 | { 46 | "filename": "Icon-App-60x60@2x.png", 47 | "idiom": "iphone", 48 | "scale": "2x", 49 | "size": "60x60" 50 | }, 51 | { 52 | "filename": "Icon-App-60x60@3x.png", 53 | "idiom": "iphone", 54 | "scale": "3x", 55 | "size": "60x60" 56 | }, 57 | { 58 | "filename": "Icon-App-20x20@1x.png", 59 | "idiom": "ipad", 60 | "scale": "1x", 61 | "size": "20x20" 62 | }, 63 | { 64 | "filename": "Icon-App-20x20@2x.png", 65 | "idiom": "ipad", 66 | "scale": "2x", 67 | "size": "20x20" 68 | }, 69 | { 70 | "filename": "Icon-App-29x29@1x.png", 71 | "idiom": "ipad", 72 | "scale": "1x", 73 | "size": "29x29" 74 | }, 75 | { 76 | "filename": "Icon-App-29x29@2x.png", 77 | "idiom": "ipad", 78 | "scale": "2x", 79 | "size": "29x29" 80 | }, 81 | { 82 | "filename": "Icon-App-40x40@1x.png", 83 | "idiom": "ipad", 84 | "scale": "1x", 85 | "size": "40x40" 86 | }, 87 | { 88 | "filename": "Icon-App-40x40@2x.png", 89 | "idiom": "ipad", 90 | "scale": "2x", 91 | "size": "40x40" 92 | }, 93 | { 94 | "filename": "Icon-App-76x76@1x.png", 95 | "idiom": "ipad", 96 | "scale": "1x", 97 | "size": "76x76" 98 | }, 99 | { 100 | "filename": "Icon-App-76x76@2x.png", 101 | "idiom": "ipad", 102 | "scale": "2x", 103 | "size": "76x76" 104 | }, 105 | { 106 | "filename": "Icon-App-83.5x83.5@2x.png", 107 | "idiom": "ipad", 108 | "scale": "2x", 109 | "size": "83.5x83.5" 110 | }, 111 | { 112 | "filename": "Icon-App-1024x1024@1x.png", 113 | "idiom": "ios-marketing", 114 | "scale": "1x", 115 | "size": "1024x1024" 116 | } 117 | ], 118 | "info": { 119 | "author": "icons_launcher", 120 | "version": 1 121 | } 122 | } -------------------------------------------------------------------------------- /lib/pages/workloads.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:go_router/go_router.dart'; 3 | import 'package:k8zdev/common/const.dart'; 4 | import 'package:k8zdev/dao/kube.dart'; 5 | import 'package:k8zdev/generated/l10n.dart'; 6 | import 'package:k8zdev/providers/current_cluster.dart'; 7 | import 'package:settings_ui/settings_ui.dart'; 8 | 9 | class WorkloadsPage extends StatefulWidget { 10 | const WorkloadsPage({super.key}); 11 | 12 | @override 13 | State createState() => _WorkloadsPageState(); 14 | } 15 | 16 | class _WorkloadsPageState extends State { 17 | late K8zCluster? cluster = CurrentCluster.current; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | var lang = S.of(context); 27 | var sections = [ 28 | // 29 | SettingsSection( 30 | title: Text(lang.applications), 31 | tiles: [ 32 | SettingsTile.navigation( 33 | title: Text(lang.helm), 34 | onPressed: (context) => 35 | GoRouter.of(context).pushNamed("helm_releases"), 36 | ), 37 | ], 38 | ), 39 | 40 | // 41 | SettingsSection( 42 | title: Text(lang.workloads), 43 | tiles: [ 44 | SettingsTile.navigation( 45 | title: Text(lang.pods), 46 | onPressed: (context) => GoRouter.of(context).pushNamed("pods"), 47 | ), 48 | SettingsTile.navigation( 49 | title: Text(lang.daemon_sets), 50 | onPressed: (context) => 51 | GoRouter.of(context).pushNamed("daemon_sets"), 52 | ), 53 | SettingsTile.navigation( 54 | title: Text(lang.deployments), 55 | onPressed: (context) => 56 | GoRouter.of(context).pushNamed("deployments"), 57 | ), 58 | SettingsTile.navigation( 59 | title: Text(lang.stateful_sets), 60 | onPressed: (context) => 61 | GoRouter.of(context).pushNamed("stateful_sets"), 62 | ), 63 | SettingsTile.navigation( 64 | title: Text(lang.replicasets), 65 | onPressed: (context) => 66 | GoRouter.of(context).pushNamed("replicasets"), 67 | ), 68 | ], 69 | ), 70 | 71 | // 72 | SettingsSection( 73 | title: Text(lang.discovery_and_lb), 74 | tiles: [ 75 | SettingsTile.navigation( 76 | title: Text(lang.endpoints), 77 | onPressed: (context) => GoRouter.of(context).pushNamed("endpoints"), 78 | ), 79 | SettingsTile.navigation( 80 | title: Text(lang.ingresses), 81 | onPressed: (context) => GoRouter.of(context).pushNamed("ingresses"), 82 | ), 83 | SettingsTile.navigation( 84 | title: Text(lang.services), 85 | onPressed: (context) => GoRouter.of(context).pushNamed("services"), 86 | ), 87 | ], 88 | ), 89 | ]; 90 | return Scaffold( 91 | appBar: AppBar(title: Text(lang.workloads)), 92 | body: Container( 93 | margin: bottomEdge, 94 | child: SettingsList(sections: sections), 95 | ), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v1.5.0 4 | 1. Iterative upgrade and bug fix: 5 | 1. fix edit kubeconfig no effect. 6 | 2. add namespace, service, service accounts, endpoints subset detail. 7 | 8 | ## v1.4.0 9 | 1. Bug fix: 10 | 1. fix: daemonsets should not has scale action. 11 | 12 | 2. Iterative upgrade: 13 | 1. implement ReplicaSets page and route, internationalization. 14 | 2. hidden init containers image pull secrets, container ID, image ID if it is null. 15 | 3. refactor pod detail section tiles, reuse it for replica sets, daemon sets, deployments and stateful sets. 16 | 4. add ownerReferences info to metadata section of detail page; add modal window to show ownerReferences detail. 17 | 18 | ## v1.3.0 19 | 1. add more Actions type for details page: logs and terminal, show creation time on the detail page. 20 | 2. add logs, terminal action button to pod detail to show logs and terminal modal. 21 | 3. implement modal widget to confirm delete resource, and real delete resource when confirmed. 22 | 4. add container id and image id to pod sections. 23 | 5. implement delete pod feature for pods list slidable pane. 24 | 6. implement pull down to refresh deployments list, network resources list, DS、STS、Pods, applications, config and storage resources list. 25 | 26 | ## v1.2.0 27 | 1. Iterative upgrade resource details page: 28 | 1. Add resourceVersion, selfLink, uid, finalizers tiles. 29 | 2. Hidden labels, annotations tiles on detail page if null. 30 | 3. Implements rendering of data in configmap, highlights data content, and adds the function of copying to clipboard. 31 | 4. Implement secret data item tiles, decode and display base64 format, and support copying field keys, encrypted data and decoded data to the clipboard. 32 | 5. Implement pod spec tiles, including: init container, container, DNS policy, host network, host name, image pull secret. 33 | 6. Displays a list of image pull secret names in a modal. 34 | 7. Use a modal box to display the image pulling strategy, image, command, parameter, environment variable, port, readiness probe, startup probe, and survival probe of the pod spec initial container and each container in the container. 35 | 36 | 2. Bug fixes and improvements: 37 | 1. When automatically matching the system language, the language code cannot be obtained correctly. 38 | 2. Refactor the current cluster provider. 39 | 40 | ## v1.1.0 41 | 1. fix: timeout config not persist saved. 42 | 2. fix: current cluster is null will panic. 43 | 3. add: slide pane to delete cluster. 44 | 4. add: action buttons to resource detail page, include scale replicas of workload sets and route to yaml info page to export resource. 45 | 5. add: details page for helm releases, endpoints, ingresses, services, configmaps, secrets, service accounts, crds, namespaces, nodes, pvcs, pvs, storage class, daemon sets, deployments, pods, stateful sets. 46 | 47 | 48 | ## v1.0.0 49 | 1. Implement add clusters from kubeconfig file. 50 | 2. Show cpu/memory etc metrics of current cluster at home page. 51 | 3. Implement resource list pages: 52 | - Nodes 53 | - Events 54 | - Namespaces 55 | - CRDs 56 | - ConfigMaps 57 | - Secrets 58 | - ServiceAccounts(SAs) 59 | - StorageClass 60 | - Persistent Volumes (PVs) 61 | - Persistent Volume Claims (PVCs) 62 | 63 | 4. Implement workload list pages: 64 | - Pods 65 | - DaemonSets 66 | - Deployments 67 | - StatefulSets 68 | - Endpoints 69 | - Ingresses 70 | - Services -------------------------------------------------------------------------------- /ios/libs/libk8z.h: -------------------------------------------------------------------------------- 1 | /* Code generated by cmd/cgo; DO NOT EDIT. */ 2 | 3 | /* package github.com/k8zdev/k8z/cmd */ 4 | 5 | 6 | #line 1 "cgo-builtin-export-prolog" 7 | 8 | #include 9 | 10 | #ifndef GO_CGO_EXPORT_PROLOGUE_H 11 | #define GO_CGO_EXPORT_PROLOGUE_H 12 | 13 | #ifndef GO_CGO_GOSTRING_TYPEDEF 14 | typedef struct { const char *p; ptrdiff_t n; } _GoString_; 15 | #endif 16 | 17 | #endif 18 | 19 | /* Start of preamble from import "C" comments. */ 20 | 21 | 22 | #line 3 "kubernetes.go" 23 | #include 24 | #include "stdint.h" 25 | 26 | #line 1 "cgo-generated-wrapper" 27 | 28 | 29 | /* End of preamble from import "C" comments. */ 30 | 31 | 32 | /* Start of boilerplate cgo prologue. */ 33 | #line 1 "cgo-gcc-export-header-prolog" 34 | 35 | #ifndef GO_CGO_PROLOGUE_H 36 | #define GO_CGO_PROLOGUE_H 37 | 38 | typedef signed char GoInt8; 39 | typedef unsigned char GoUint8; 40 | typedef short GoInt16; 41 | typedef unsigned short GoUint16; 42 | typedef int GoInt32; 43 | typedef unsigned int GoUint32; 44 | typedef long long GoInt64; 45 | typedef unsigned long long GoUint64; 46 | typedef GoInt64 GoInt; 47 | typedef GoUint64 GoUint; 48 | typedef size_t GoUintptr; 49 | typedef float GoFloat32; 50 | typedef double GoFloat64; 51 | #ifdef _MSC_VER 52 | #include 53 | typedef _Fcomplex GoComplex64; 54 | typedef _Dcomplex GoComplex128; 55 | #else 56 | typedef float _Complex GoComplex64; 57 | typedef double _Complex GoComplex128; 58 | #endif 59 | 60 | /* 61 | static assertion to make sure the file is being used on architecture 62 | at least with matching size of GoInt. 63 | */ 64 | typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; 65 | 66 | #ifndef GO_CGO_GOSTRING_TYPEDEF 67 | typedef _GoString_ GoString; 68 | #endif 69 | typedef void *GoMap; 70 | typedef void *GoChan; 71 | typedef struct { void *t; void *v; } GoInterface; 72 | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; 73 | 74 | #endif 75 | 76 | /* End of boilerplate cgo prologue. */ 77 | 78 | #ifdef __cplusplus 79 | extern "C" { 80 | #endif 81 | 82 | 83 | // PinStatic do nothing, call it at swift just keep the static libarary be linked. 84 | // 85 | extern void PinStatic(); 86 | 87 | // FreePointer can be used to free a returned pointer. 88 | // 89 | extern void FreePointer(char* ptr); 90 | 91 | // LocalServerAddr return listen address of web server. 92 | // 93 | extern char* LocalServerAddr(); 94 | 95 | // StartLocalServer starts an Go server. 96 | // 97 | extern void StartLocalServer(); 98 | extern char* Json2yaml(char* src, int len); 99 | 100 | /* Return type for MultiParams */ 101 | struct MultiParams_return { 102 | char* r0; /* body */ 103 | char* r1; /* err */ 104 | }; 105 | extern struct MultiParams_return MultiParams(char* name, int nameLen, int64_t timeout, _Bool insecure); 106 | 107 | /* Return type for K8zRequest */ 108 | struct K8zRequest_return { 109 | char* r0; /* respC */ 110 | char* r1; /* errC */ 111 | }; 112 | 113 | // K8zRequest 114 | // 115 | extern struct K8zRequest_return K8zRequest(char* serverC, int serverLen, char* caDataC, int caDataLen, _Bool insecureC, char* clientCertC, int clientCertLen, char* clientKeyC, int clientKeyLen, char* tokenC, int tokenLen, char* usernameC, int usernameLen, char* passwordC, int passwordLen, char* proxyC, int proxyLen, int64_t timeoutC, char* methodC, int mehtodLen, char* apiC, int apiLen, char* bodyC, int bodyLen); 116 | 117 | #ifdef __cplusplus 118 | } 119 | #endif 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/k8zdev/k8z 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | // toolchain go1.21.4 8 | 9 | require ( 10 | github.com/gin-gonic/gin v1.9.1 11 | github.com/gorilla/websocket v1.5.1 12 | github.com/sirupsen/logrus v1.9.3 13 | golang.org/x/mobile v0.0.0-20240112133503-c713f31d574b 14 | k8s.io/apimachinery v0.32.3 15 | k8s.io/client-go v0.32.3 16 | ) 17 | 18 | require ( 19 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 20 | github.com/google/go-cmp v0.6.0 // indirect 21 | github.com/pkg/errors v0.9.1 // indirect 22 | github.com/x448/float16 v0.8.4 // indirect 23 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 24 | ) 25 | 26 | require ( 27 | github.com/bytedance/sonic v1.9.1 // indirect 28 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 29 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 30 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 31 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 32 | github.com/gin-contrib/sse v0.1.0 // indirect 33 | github.com/go-logr/logr v1.4.2 // indirect 34 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 35 | github.com/go-openapi/jsonreference v0.20.2 // indirect 36 | github.com/go-openapi/swag v0.23.0 // indirect 37 | github.com/go-playground/locales v0.14.1 // indirect 38 | github.com/go-playground/universal-translator v0.18.1 // indirect 39 | github.com/go-playground/validator/v10 v10.14.0 // indirect 40 | github.com/goccy/go-json v0.10.2 // indirect 41 | github.com/gogo/protobuf v1.3.2 // indirect 42 | github.com/golang/protobuf v1.5.4 // indirect 43 | github.com/google/gnostic-models v0.6.8 // indirect 44 | github.com/google/gofuzz v1.2.0 // indirect 45 | github.com/google/uuid v1.6.0 // indirect 46 | github.com/josharian/intern v1.0.0 // indirect 47 | github.com/json-iterator/go v1.1.12 // indirect 48 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 49 | github.com/leodido/go-urn v1.2.4 // indirect 50 | github.com/mailru/easyjson v0.7.7 // indirect 51 | github.com/mattn/go-isatty v0.0.19 // indirect 52 | github.com/moby/spdystream v0.5.0 // indirect 53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 54 | github.com/modern-go/reflect2 v1.0.2 // indirect 55 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 56 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 57 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 58 | github.com/spf13/pflag v1.0.5 // indirect 59 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 60 | github.com/ugorji/go/codec v1.2.11 // indirect 61 | golang.org/x/arch v0.3.0 // indirect 62 | golang.org/x/crypto v0.31.0 // indirect 63 | golang.org/x/mod v0.21.0 // indirect 64 | golang.org/x/net v0.33.0 // indirect 65 | golang.org/x/oauth2 v0.23.0 // indirect 66 | golang.org/x/sync v0.10.0 // indirect 67 | golang.org/x/sys v0.28.0 // indirect 68 | golang.org/x/term v0.27.0 // indirect 69 | golang.org/x/text v0.21.0 // indirect 70 | golang.org/x/time v0.7.0 // indirect 71 | golang.org/x/tools v0.26.0 // indirect 72 | google.golang.org/protobuf v1.35.1 // indirect 73 | gopkg.in/inf.v0 v0.9.1 // indirect 74 | gopkg.in/yaml.v3 v3.0.1 // indirect 75 | k8s.io/api v0.32.3 // direct 76 | k8s.io/klog/v2 v2.130.1 // indirect 77 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect 78 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 79 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 80 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect 81 | sigs.k8s.io/yaml v1.4.0 82 | ) 83 | -------------------------------------------------------------------------------- /lib/models/kubernetes/helper.dart: -------------------------------------------------------------------------------- 1 | // @dart=2.12 2 | 3 | // ignore_for_file: unused_element, unused_import 4 | // ignore_for_file: always_put_required_named_parameters_first 5 | // ignore_for_file: constant_identifier_names 6 | // ignore_for_file: lines_longer_than_80_chars 7 | part of models.k8s; 8 | 9 | const _delimiters = {'csv': ',', 'ssv': ' ', 'tsv': '\t', 'pipes': '|'}; 10 | const _dateEpochMarker = 'epoch'; 11 | final _regList = RegExp(r'^List<(.*)>$'); 12 | final _regSet = RegExp(r'^Set<(.*)>$'); 13 | final _regMap = RegExp(r'^Map$'); 14 | const _deepEquality = DeepCollectionEquality(); 15 | final _dateFormatter = DateFormat('yyyy-MM-dd'); 16 | 17 | bool _isEpochMarker(String? pattern) => 18 | pattern == _dateEpochMarker || pattern == '/$_dateEpochMarker/'; 19 | 20 | class QueryParam { 21 | const QueryParam(this.name, this.value); 22 | 23 | final String name; 24 | final String value; 25 | 26 | @override 27 | String toString() => 28 | '${Uri.encodeQueryComponent(name)}=${Uri.encodeQueryComponent(value)}'; 29 | } 30 | 31 | // Ported from the Java version. 32 | Iterable _queryParams( 33 | String collectionFormat, 34 | String name, 35 | dynamic value, 36 | ) { 37 | // Assertions to run in debug mode only. 38 | assert(name.isNotEmpty, 'Parameter cannot be an empty string.'); 39 | 40 | final params = []; 41 | 42 | if (value is List) { 43 | if (collectionFormat == 'multi') { 44 | return value.map( 45 | (dynamic v) => QueryParam(name, parameterToString(v)), 46 | ); 47 | } 48 | 49 | // Default collection format is 'csv'. 50 | if (collectionFormat.isEmpty) { 51 | collectionFormat = 'csv'; // ignore: parameter_assignments 52 | } 53 | 54 | final delimiter = _delimiters[collectionFormat] ?? ','; 55 | 56 | params.add(QueryParam( 57 | name, 58 | value.map(parameterToString).join(delimiter), 59 | )); 60 | } else if (value != null) { 61 | params.add(QueryParam(name, parameterToString(value))); 62 | } 63 | 64 | return params; 65 | } 66 | 67 | /// Format the given parameter object into a [String]. 68 | String parameterToString(dynamic value) { 69 | if (value == null) { 70 | return ''; 71 | } 72 | if (value is DateTime) { 73 | return value.toUtc().toIso8601String(); 74 | } 75 | return value.toString(); 76 | } 77 | 78 | /// Returns a valid [T] value found at the specified Map [key], null otherwise. 79 | T? mapValueOfType(dynamic map, String key) { 80 | final dynamic value = map is Map ? map[key] : null; 81 | return value is T ? value : null; 82 | } 83 | 84 | /// Returns a valid Map>K, V< found at the specified Map [key], null otherwise. 85 | Map? mapCastOfType(dynamic map, String key) { 86 | final dynamic value = map is Map ? map[key] : null; 87 | return value is Map ? value.cast() : null; 88 | } 89 | 90 | /// Returns a valid [DateTime] found at the specified Map [key], null otherwise. 91 | DateTime? mapDateTime(dynamic map, String key, [String? pattern]) { 92 | final dynamic value = map is Map ? map[key] : null; 93 | if (value != null) { 94 | int? millis; 95 | if (value is int) { 96 | millis = value; 97 | } else if (value is String) { 98 | if (_isEpochMarker(pattern)) { 99 | millis = int.tryParse(value); 100 | } else { 101 | return DateTime.tryParse(value); 102 | } 103 | } 104 | if (millis != null) { 105 | return DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true); 106 | } 107 | } 108 | return null; 109 | } 110 | -------------------------------------------------------------------------------- /lib/pages/k8s_list/cluster/select_clusters.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_multi_select_items/flutter_multi_select_items.dart'; 3 | import 'package:go_router/go_router.dart'; 4 | import 'package:k8zdev/common/ops.dart'; 5 | import 'package:k8zdev/common/styles.dart'; 6 | import 'package:k8zdev/dao/kube.dart'; 7 | import 'package:k8zdev/generated/l10n.dart'; 8 | import 'package:k8zdev/providers/current_cluster.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class ChoiceClustersSubPage extends StatefulWidget { 12 | final List clusters; 13 | const ChoiceClustersSubPage({super.key, required this.clusters}); 14 | 15 | @override 16 | State createState() => _ChoiceClustersSubPageState(); 17 | } 18 | 19 | class _ChoiceClustersSubPageState extends State { 20 | List _selected = []; 21 | 22 | Widget getChild(BuildContext context, K8zCluster config) { 23 | var screenWith = MediaQuery.of(context).size.width; 24 | return Container( 25 | width: screenWith - 36, 26 | padding: defaultEdge, 27 | child: OverflowBar( 28 | alignment: MainAxisAlignment.center, 29 | children: [const Icon(Icons.computer), Text(config.name)], 30 | ), 31 | ); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | var lang = S.of(context); 37 | 38 | return Scaffold( 39 | appBar: AppBar(title: Text(lang.save_clusters)), 40 | floatingActionButton: FloatingActionButton( 41 | child: const Icon(Icons.save), 42 | onPressed: () async { 43 | talker.debug("save selectedItems: $_selected"); 44 | try { 45 | K8zCluster.batchInsert(_selected); 46 | if (_selected.isNotEmpty) { 47 | final ccProvider = 48 | Provider.of(context, listen: false); 49 | ccProvider.setCurrent(_selected[0]); 50 | } 51 | GoRouter.of(context).go("/clusters"); 52 | } catch (err) { 53 | talker.error("insert failed: ${err.toString()}"); 54 | } 55 | }, 56 | ), 57 | body: Container( 58 | alignment: Alignment.topCenter, 59 | padding: defaultEdge, 60 | child: Column( 61 | children: [ 62 | const Divider(height: 18, color: Colors.transparent), 63 | if (widget.clusters.isEmpty) 64 | Column( 65 | children: [ 66 | Center(child: Text(lang.empyt_context)), 67 | const Divider(height: 18, color: Colors.transparent), 68 | ], 69 | ), 70 | if (widget.clusters.isNotEmpty) 71 | Column( 72 | children: [ 73 | Center(child: Text(lang.select_clusters)), 74 | const Divider(height: 18, color: Colors.transparent), 75 | ], 76 | ), 77 | SingleChildScrollView( 78 | child: MultiSelectContainer( 79 | items: widget.clusters.map((config) { 80 | return MultiSelectCard( 81 | value: config, 82 | child: getChild(context, config), 83 | ); 84 | }).toList(), 85 | onChange: (selectedItems, selectedItem) { 86 | setState(() => _selected = selectedItems); 87 | talker.debug("selectedItem: ${selectedItem.name}"); 88 | }, 89 | ), 90 | ), 91 | ], 92 | ), 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/pages/resources.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:go_router/go_router.dart'; 3 | import 'package:k8zdev/common/const.dart'; 4 | import 'package:k8zdev/dao/kube.dart'; 5 | import 'package:k8zdev/generated/l10n.dart'; 6 | import 'package:k8zdev/providers/current_cluster.dart'; 7 | import 'package:settings_ui/settings_ui.dart'; 8 | 9 | class ResourcesPage extends StatefulWidget { 10 | const ResourcesPage({super.key}); 11 | 12 | @override 13 | State createState() => _ResourcesPageState(); 14 | } 15 | 16 | class _ResourcesPageState extends State { 17 | late K8zCluster? cluster = CurrentCluster.current; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | } 23 | 24 | AbstractSettingsSection clusterSection(S lang) { 25 | return SettingsSection( 26 | title: Text(lang.clusters), 27 | tiles: [ 28 | SettingsTile.navigation( 29 | title: Text(lang.nodes), 30 | onPressed: (context) => GoRouter.of(context).pushNamed("nodes"), 31 | ), 32 | SettingsTile.navigation( 33 | title: Text(lang.events), 34 | onPressed: (context) => GoRouter.of(context).pushNamed("events"), 35 | ), 36 | SettingsTile.navigation( 37 | title: Text(lang.namespaces), 38 | onPressed: (context) => GoRouter.of(context).pushNamed("namespaces"), 39 | ), 40 | SettingsTile.navigation( 41 | title: Text(lang.crds), 42 | onPressed: (context) => GoRouter.of(context).pushNamed("crds"), 43 | ), 44 | ], 45 | ); 46 | } 47 | 48 | AbstractSettingsSection configSection(S lang) { 49 | return SettingsSection( 50 | title: Text(lang.config), 51 | tiles: [ 52 | SettingsTile.navigation( 53 | title: Text(lang.config_maps), 54 | onPressed: (context) => GoRouter.of(context).pushNamed("config_maps"), 55 | ), 56 | SettingsTile.navigation( 57 | title: Text(lang.secrets), 58 | onPressed: (context) => GoRouter.of(context).pushNamed("secrets"), 59 | ), 60 | SettingsTile.navigation( 61 | title: Text(lang.service_accounts), 62 | onPressed: (context) => 63 | GoRouter.of(context).pushNamed("service_accounts"), 64 | ), 65 | ], 66 | ); 67 | } 68 | 69 | AbstractSettingsSection storageSection(S lang) { 70 | return SettingsSection( 71 | title: Text(lang.storage), 72 | tiles: [ 73 | SettingsTile.navigation( 74 | title: Text(lang.storage_class), 75 | onPressed: (context) => 76 | GoRouter.of(context).pushNamed("storage_class"), 77 | ), 78 | SettingsTile.navigation( 79 | title: Text(lang.pvs), 80 | onPressed: (context) => GoRouter.of(context).pushNamed("pvs"), 81 | ), 82 | SettingsTile.navigation( 83 | title: Text(lang.pvcs), 84 | onPressed: (context) => GoRouter.of(context).pushNamed("pvcs"), 85 | ), 86 | ], 87 | ); 88 | } 89 | 90 | @override 91 | Widget build(BuildContext context) { 92 | var lang = S.of(context); 93 | return Scaffold( 94 | appBar: AppBar(title: Text(lang.resources)), 95 | body: Container( 96 | margin: bottomEdge, 97 | child: SettingsList( 98 | sections: [ 99 | clusterSection(lang), 100 | configSection(lang), 101 | storageSection(lang), 102 | ], 103 | ), 104 | ), 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/pages/k8s_detail/yaml_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:code_editor/code_editor.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:k8zdev/common/helpers.dart'; 7 | import 'package:k8zdev/generated/l10n.dart'; 8 | import 'package:k8zdev/services/k8z_native.dart'; 9 | import 'package:path_provider/path_provider.dart'; 10 | import 'package:json2yaml/json2yaml.dart'; 11 | 12 | class YamlPage extends StatefulWidget { 13 | final String itemUrl; 14 | final String fileName; 15 | final JsonReturn resp; 16 | const YamlPage({ 17 | super.key, 18 | required this.itemUrl, 19 | required this.fileName, 20 | required this.resp, 21 | }); 22 | 23 | @override 24 | State createState() => _YamlPageState(); 25 | } 26 | 27 | class _YamlPageState extends State { 28 | late String content; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | content = json2yaml( 34 | widget.resp.body, 35 | yamlStyle: YamlStyle.pubspecYaml, 36 | ); 37 | } 38 | 39 | double buttonsHeight() { 40 | if (Platform.isIOS) { 41 | return 270; 42 | } 43 | if (Platform.isMacOS) { 44 | return 200; 45 | } 46 | return 0; 47 | } 48 | 49 | Future exportFile(S lang) async { 50 | // ony for iOS 51 | final directory = await getApplicationDocumentsDirectory(); 52 | final fileName = "exported/${widget.fileName}.yaml"; 53 | final file = 54 | await File("${directory.path}/$fileName").create(recursive: true); 55 | 56 | final f = file.writeAsString(content, flush: true); 57 | // ignore: use_build_context_synchronously 58 | ScaffoldMessenger.of(context).showSnackBar( 59 | SnackBar( 60 | showCloseIcon: true, 61 | closeIconColor: Colors.white, 62 | backgroundColor: Colors.green, 63 | content: Text( 64 | lang.exported(fileName), 65 | ), 66 | ), 67 | ); 68 | return f; 69 | } 70 | 71 | Widget yamlEditor(S lang, JsonReturn resp, double appbarHeight) { 72 | var buttons = Row( 73 | mainAxisAlignment: MainAxisAlignment.end, 74 | children: [ 75 | SizedBox( 76 | height: 50, 77 | child: TextButton.icon( 78 | onPressed: () async { 79 | await exportFile(lang); 80 | }, 81 | label: Text(lang.export), 82 | icon: const Icon(Icons.save_rounded), 83 | ), 84 | ), 85 | const Divider(indent: 10), 86 | ], 87 | ); 88 | 89 | return Column( 90 | children: [ 91 | CodeEditor( 92 | model: EditorModel( 93 | files: [ 94 | FileEditor( 95 | name: "${widget.fileName}.yaml", 96 | language: "yaml", 97 | code: content, 98 | readonly: true, 99 | ) 100 | ], 101 | styleOptions: EditorModelStyleOptions( 102 | showToolbar: false, 103 | editButtonName: lang.edit, 104 | heightOfContainer: 105 | availableHeight(context, appbarHeight + buttonsHeight()), 106 | ), 107 | ), 108 | formatters: const ["yaml"], 109 | ), 110 | 111 | const Divider(height: 10, color: Colors.transparent), 112 | // buttons 113 | buttons, 114 | ], 115 | ); 116 | } 117 | 118 | @override 119 | Widget build(BuildContext context) { 120 | final lang = S.of(context); 121 | final appbar = AppBar(title: Text(lang.resource_yaml)); 122 | return Scaffold( 123 | appBar: appbar, 124 | body: yamlEditor( 125 | lang, 126 | widget.resp, 127 | appbar.preferredSize.height, 128 | ), 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/widgets/detail_widgets/configmap.dart: -------------------------------------------------------------------------------- 1 | import 'package:clipboard/clipboard.dart'; 2 | import 'package:collection/collection.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_highlighting/flutter_highlighting.dart'; 5 | import 'package:flutter_highlighting/themes/github-dark-dimmed.dart'; 6 | import 'package:highlighting/languages/yaml.dart'; 7 | import 'package:k8zdev/common/styles.dart'; 8 | import 'package:k8zdev/generated/l10n.dart'; 9 | import 'package:k8zdev/models/models.dart'; 10 | import 'package:k8zdev/widgets/modal.dart'; 11 | import 'package:k8zdev/widgets/widgets.dart'; 12 | import 'package:settings_ui/settings_ui.dart'; 13 | 14 | List buildConfigMapDetailSectionTiels( 15 | BuildContext context, 16 | IoK8sApiCoreV1ConfigMap? cm, 17 | String langCode, 18 | ) { 19 | final lang = S.of(context); 20 | 21 | List tiles = []; 22 | 23 | if (cm == null || cm.data.isEmpty) { 24 | return [SettingsTile(title: Text(lang.empty))]; 25 | } 26 | 27 | cm.data.entries.forEachIndexed((index, element) { 28 | tiles.add( 29 | SettingsTile.navigation( 30 | title: leadingText(element.key, langCode), 31 | value: Text(element.value), 32 | trailing: IconButton( 33 | onPressed: () async { 34 | await FlutterClipboard.copy(element.value); 35 | }, 36 | icon: const Icon(Icons.copy), 37 | ), 38 | onPressed: (context) { 39 | showModal( 40 | context, 41 | Column( 42 | crossAxisAlignment: CrossAxisAlignment.stretch, 43 | children: [ 44 | Row( 45 | mainAxisAlignment: MainAxisAlignment.center, 46 | children: [ 47 | Text(element.key), 48 | const Divider(indent: 10), 49 | ElevatedButton.icon( 50 | onPressed: () async { 51 | await FlutterClipboard.copy(element.key); 52 | }, 53 | icon: const Icon( 54 | Icons.key, 55 | size: 12, 56 | ), 57 | label: const Text( 58 | "copy key", 59 | style: TextStyle(fontSize: 12), 60 | ), 61 | ), 62 | const Divider(indent: 10), 63 | ElevatedButton.icon( 64 | onPressed: () async { 65 | await FlutterClipboard.copy(element.value); 66 | }, 67 | icon: const Icon( 68 | Icons.text_format, 69 | size: 12, 70 | ), 71 | label: const Text( 72 | "copy value", 73 | style: TextStyle(fontSize: 12), 74 | ), 75 | ), 76 | ], 77 | ), 78 | Container( 79 | height: 368, 80 | padding: const EdgeInsets.only(top: 10), 81 | child: SingleChildScrollView( 82 | // can not selectable https://github.com/akvelon/dart-highlighting/pull/71/files 83 | child: HighlightView( 84 | element.value, 85 | languageId: yaml.id, 86 | theme: githubDarkDimmedTheme, 87 | padding: defaultEdge, 88 | textStyle: const TextStyle( 89 | fontSize: 16, 90 | ), 91 | ), 92 | ), 93 | ), 94 | ], 95 | ), 96 | ); 97 | }, 98 | ), 99 | ); 100 | // 101 | }); 102 | return tiles; 103 | } 104 | -------------------------------------------------------------------------------- /lib/widgets/virtual_keyboard.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:k8zdev/common/ops.dart'; 3 | import 'package:xterm/xterm.dart'; 4 | 5 | class VirtualKeyboardView extends StatelessWidget { 6 | const VirtualKeyboardView(this.keyboard, this.terminal, {super.key}); 7 | 8 | final VirtualKeyboard keyboard; 9 | final Terminal terminal; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final body = AnimatedBuilder( 14 | animation: keyboard, 15 | builder: (context, child) => ToggleButtons( 16 | isSelected: [ 17 | keyboard.ctrl, 18 | keyboard.alt, 19 | keyboard.shift, 20 | false, 21 | false, 22 | false, 23 | false, 24 | false 25 | ], 26 | onPressed: (index) { 27 | switch (index) { 28 | case 0: 29 | keyboard.ctrl = !keyboard.ctrl; 30 | break; 31 | case 1: 32 | keyboard.alt = !keyboard.alt; 33 | break; 34 | case 2: 35 | keyboard.shift = !keyboard.shift; 36 | break; 37 | case 3: 38 | keyboard.ctrl = false; 39 | keyboard.alt = false; 40 | keyboard.shift = false; 41 | terminal.keyInput(TerminalKey.tab); 42 | break; 43 | case 4: 44 | keyboard.ctrl = false; 45 | keyboard.alt = false; 46 | keyboard.shift = false; 47 | terminal.keyInput(TerminalKey.arrowLeft); 48 | break; 49 | case 5: 50 | keyboard.ctrl = false; 51 | keyboard.alt = false; 52 | keyboard.shift = false; 53 | terminal.keyInput(TerminalKey.arrowUp); 54 | break; 55 | case 6: 56 | keyboard.ctrl = false; 57 | keyboard.alt = false; 58 | keyboard.shift = false; 59 | terminal.keyInput(TerminalKey.arrowDown); 60 | break; 61 | case 7: 62 | keyboard.ctrl = false; 63 | keyboard.alt = false; 64 | keyboard.shift = false; 65 | terminal.keyInput(TerminalKey.arrowRight); 66 | break; 67 | } 68 | }, 69 | children: const [ 70 | Text('Ctrl'), 71 | Text('Alt'), 72 | Text('Shift'), 73 | Text('Tab'), 74 | Text('←'), 75 | Text('↑'), 76 | Text('↓'), 77 | Text('→'), 78 | ], 79 | ), 80 | ); 81 | return SingleChildScrollView( 82 | scrollDirection: Axis.horizontal, 83 | child: body, 84 | ); 85 | } 86 | } 87 | 88 | class VirtualKeyboard extends TerminalInputHandler with ChangeNotifier { 89 | final TerminalInputHandler inputHandler; 90 | 91 | VirtualKeyboard(this.inputHandler); 92 | 93 | bool _ctrl = false; 94 | 95 | bool get ctrl => _ctrl; 96 | 97 | set ctrl(bool value) { 98 | if (_ctrl != value) { 99 | _ctrl = value; 100 | notifyListeners(); 101 | } 102 | } 103 | 104 | bool _shift = false; 105 | 106 | bool get shift => _shift; 107 | 108 | set shift(bool value) { 109 | if (_shift != value) { 110 | _shift = value; 111 | notifyListeners(); 112 | } 113 | } 114 | 115 | bool _alt = false; 116 | 117 | bool get alt => _alt; 118 | 119 | set alt(bool value) { 120 | if (_alt != value) { 121 | _alt = value; 122 | notifyListeners(); 123 | } 124 | } 125 | 126 | @override 127 | String? call(TerminalKeyboardEvent event) { 128 | talker.warning( 129 | "key: ${event.key.toString()} ${event.state}, ctrl: ${event.ctrl}, shift: ${event.shift}, alt: ${event.alt}"); 130 | return inputHandler.call(event.copyWith( 131 | ctrl: event.ctrl || _ctrl, 132 | shift: event.shift || _shift, 133 | alt: event.alt || _alt, 134 | )); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_discovery_v1_for_zone.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiDiscoveryV1ForZone { 14 | /// Returns a new [IoK8sApiDiscoveryV1ForZone] instance. 15 | IoK8sApiDiscoveryV1ForZone({ 16 | required this.name, 17 | }); 18 | 19 | /// name represents the name of the zone. 20 | String name; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiDiscoveryV1ForZone && 24 | other.name == name; 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (name.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiDiscoveryV1ForZone[name=$name]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'name'] = this.name; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiDiscoveryV1ForZone] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiDiscoveryV1ForZone? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiDiscoveryV1ForZone[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiDiscoveryV1ForZone[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiDiscoveryV1ForZone( 59 | name: mapValueOfType(json, r'name')!, 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiDiscoveryV1ForZone.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiDiscoveryV1ForZone.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiDiscoveryV1ForZone-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiDiscoveryV1ForZone.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | 'name', 108 | }; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /lib/services/k8z_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:k8zdev/common/ops.dart'; 3 | import 'package:k8zdev/dao/kube.dart'; 4 | import 'package:k8zdev/providers/current_cluster.dart'; 5 | import 'package:k8zdev/providers/timeout.dart'; 6 | import 'package:k8zdev/services/k8z_native.dart'; 7 | import 'package:http/http.dart' as http; 8 | import 'package:provider/provider.dart'; 9 | 10 | final noneCurrentCluster = ErrorDescription( 11 | "cluster is null, and not get current cluster from context", 12 | ); 13 | 14 | /// [K8zService] implements a service to interactiv with Kubernetes cluster. 15 | /// 16 | /// To use [K8zService] must provide cluster info when init this service. 17 | class K8zService { 18 | K8zCluster cluster; 19 | String proxy; 20 | int timeout; 21 | 22 | K8zService( 23 | BuildContext context, { 24 | required this.cluster, 25 | this.proxy = "", 26 | this.timeout = 60, 27 | }) { 28 | final timeoutProvider = 29 | Provider.of(context, listen: false); 30 | final timeout = timeoutProvider.timeout; 31 | this.timeout = timeout; 32 | } 33 | 34 | /// [isStarted] used to check local server is started. 35 | /// The local server is used to proxy the request to the Kubernetes cluster. 36 | static Future isStarted() async { 37 | try { 38 | var resp = await http.get( 39 | Uri(scheme: "http", host: "localhost", port: 29257, path: "/ping"), 40 | ); 41 | if (resp.statusCode == 200) { 42 | return true; 43 | } 44 | talker.error( 45 | "check local server failed, error: status=${resp.statusCode},body=${resp.body}"); 46 | } catch (e) { 47 | talker.error("check local server failed, error: $e"); 48 | } 49 | return false; 50 | } 51 | 52 | /// [checkHealth] used to check the Kubernetes cluster is health. 53 | /// 54 | Future checkHealth() async { 55 | var resp = await K8zNative() 56 | .k8zRequestRaw(cluster, proxy, timeout, "GET", "/readyz", ""); 57 | if (resp.body == "ok") { 58 | return true; 59 | } 60 | talker.error("check failed, error ${resp.error}"); 61 | return false; 62 | } 63 | 64 | /// [get] it is used to run the GET request on the Kubernetes cluster. 65 | /// 66 | Future get(String api) async { 67 | return K8zNative().k8zRequest(cluster, proxy, timeout, "GET", api, ""); 68 | } 69 | 70 | /// [delete] it is used to run the DELETE request on the Kubernetes cluster. 71 | /// 72 | Future delete(String api, String body) async { 73 | return K8zNative().k8zRequest(cluster, proxy, timeout, "DELETE", api, body); 74 | } 75 | 76 | /// [patch] it is used to run the PATCH request on the Kubernetes cluster. 77 | /// The [body] must be a valid json patch. 78 | Future patch(String api, String body) async { 79 | return K8zNative().k8zRequest(cluster, proxy, timeout, "PATCH", api, body); 80 | } 81 | 82 | /// [post] it is used to run the POST request on the Kubernetes cluster. 83 | /// 84 | Future post(String api, String body) async { 85 | return K8zNative().k8zRequest(cluster, proxy, timeout, "POST", api, body); 86 | } 87 | } 88 | 89 | Future fetchCurrentRes( 90 | BuildContext context, String path, String resource, 91 | {namespaced = true, listen = true, String? query}) async { 92 | final cluster = Provider.of(context, listen: listen).cluster; 93 | if (cluster == null) { 94 | talker.error("null cluster"); 95 | throw noneCurrentCluster; 96 | } 97 | 98 | String api; 99 | if (namespaced && cluster.namespace.isNotEmpty) { 100 | final ns = "/namespaces/${cluster.namespace}"; 101 | api = "$path$ns/$resource"; 102 | } else { 103 | api = "$path/$resource"; 104 | } 105 | 106 | if (query != null && query.isNotEmpty) { 107 | api = "$api?$query"; 108 | } 109 | 110 | final resp = await K8zService(context, cluster: cluster).get(api); 111 | 112 | return resp; 113 | } 114 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_core_v1_daemon_endpoint.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiCoreV1DaemonEndpoint { 14 | /// Returns a new [IoK8sApiCoreV1DaemonEndpoint] instance. 15 | IoK8sApiCoreV1DaemonEndpoint({ 16 | required this.port, 17 | }); 18 | 19 | /// Port number of the given endpoint. 20 | int port; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiCoreV1DaemonEndpoint && 24 | other.port == port; 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (port.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiCoreV1DaemonEndpoint[port=$port]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'Port'] = this.port; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiCoreV1DaemonEndpoint] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiCoreV1DaemonEndpoint? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiCoreV1DaemonEndpoint[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiCoreV1DaemonEndpoint[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiCoreV1DaemonEndpoint( 59 | port: mapValueOfType(json, r'Port')!, 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiCoreV1DaemonEndpoint.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiCoreV1DaemonEndpoint.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiCoreV1DaemonEndpoint-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiCoreV1DaemonEndpoint.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | 'Port', 108 | }; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /lib/widgets/detail_widgets/secret.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:clipboard/clipboard.dart'; 4 | import 'package:collection/collection.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_highlighting/flutter_highlighting.dart'; 7 | import 'package:flutter_highlighting/themes/github-dark-dimmed.dart'; 8 | import 'package:highlighting/languages/yaml.dart'; 9 | import 'package:k8zdev/common/styles.dart'; 10 | import 'package:k8zdev/generated/l10n.dart'; 11 | import 'package:k8zdev/models/models.dart'; 12 | import 'package:k8zdev/widgets/modal.dart'; 13 | import 'package:k8zdev/widgets/widgets.dart'; 14 | import 'package:settings_ui/settings_ui.dart'; 15 | 16 | List buildSecretDetailSectionTiels( 17 | BuildContext context, 18 | IoK8sApiCoreV1Secret? secret, 19 | String langCode, 20 | ) { 21 | final lang = S.of(context); 22 | 23 | List tiles = []; 24 | 25 | if (secret == null || secret.data.isEmpty) { 26 | return [SettingsTile(title: Text(lang.empty))]; 27 | } 28 | 29 | secret.data.entries.forEachIndexed((index, element) { 30 | tiles.add( 31 | SettingsTile.navigation( 32 | title: leadingText(element.key, langCode), 33 | value: Text(element.value), 34 | trailing: IconButton( 35 | onPressed: () async { 36 | await FlutterClipboard.copy(element.value); 37 | }, 38 | icon: const Icon(Icons.copy), 39 | ), 40 | onPressed: (context) { 41 | final decoded = utf8.decode(base64Decode(element.value)); 42 | showModal( 43 | context, 44 | Column( 45 | crossAxisAlignment: CrossAxisAlignment.stretch, 46 | children: [ 47 | Row( 48 | mainAxisAlignment: MainAxisAlignment.center, 49 | children: [ 50 | Text(element.key), 51 | const Divider(indent: 10), 52 | ElevatedButton.icon( 53 | onPressed: () async { 54 | await FlutterClipboard.copy(element.key); 55 | }, 56 | icon: const Icon( 57 | Icons.key, 58 | size: 12, 59 | ), 60 | label: const Text( 61 | "copy key", 62 | style: TextStyle(fontSize: 12), 63 | ), 64 | ), 65 | const Divider(indent: 10), 66 | ElevatedButton.icon( 67 | onPressed: () async { 68 | await FlutterClipboard.copy(decoded); 69 | }, 70 | icon: const Icon( 71 | Icons.text_format, 72 | size: 12, 73 | ), 74 | label: const Text( 75 | "copy decoded", 76 | style: TextStyle(fontSize: 12), 77 | ), 78 | ), 79 | ], 80 | ), 81 | Container( 82 | height: 368, 83 | padding: const EdgeInsets.only(top: 10), 84 | child: SingleChildScrollView( 85 | // can not selectable https://github.com/akvelon/dart-highlighting/pull/71/files 86 | child: HighlightView( 87 | decoded, 88 | languageId: yaml.id, 89 | theme: githubDarkDimmedTheme, 90 | padding: defaultEdge, 91 | textStyle: const TextStyle( 92 | fontSize: 16, 93 | ), 94 | ), 95 | ), 96 | ), 97 | ], 98 | ), 99 | ); 100 | }, 101 | ), 102 | ); 103 | // 104 | }); 105 | 106 | return tiles; 107 | } 108 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_node_v1_overhead.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiNodeV1Overhead { 14 | /// Returns a new [IoK8sApiNodeV1Overhead] instance. 15 | IoK8sApiNodeV1Overhead({ 16 | this.podFixed = const {}, 17 | }); 18 | 19 | /// PodFixed represents the fixed resource overhead associated with running a pod. 20 | Map podFixed; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiNodeV1Overhead && 24 | _deepEquality.equals(other.podFixed, podFixed); 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (podFixed.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiNodeV1Overhead[podFixed=$podFixed]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'podFixed'] = this.podFixed; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiNodeV1Overhead] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiNodeV1Overhead? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiNodeV1Overhead[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiNodeV1Overhead[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiNodeV1Overhead( 59 | podFixed: mapCastOfType(json, r'podFixed') ?? const {}, 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiNodeV1Overhead.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiNodeV1Overhead.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiNodeV1Overhead-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiNodeV1Overhead.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | }; 108 | } 109 | 110 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_core_v1_limit_range_spec.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiCoreV1LimitRangeSpec { 14 | /// Returns a new [IoK8sApiCoreV1LimitRangeSpec] instance. 15 | IoK8sApiCoreV1LimitRangeSpec({ 16 | this.limits = const [], 17 | }); 18 | 19 | /// Limits is the list of LimitRangeItem objects that are enforced. 20 | List limits; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiCoreV1LimitRangeSpec && 24 | _deepEquality.equals(other.limits, limits); 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (limits.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiCoreV1LimitRangeSpec[limits=$limits]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'limits'] = this.limits; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiCoreV1LimitRangeSpec] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiCoreV1LimitRangeSpec? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiCoreV1LimitRangeSpec[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiCoreV1LimitRangeSpec[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiCoreV1LimitRangeSpec( 59 | limits: IoK8sApiCoreV1LimitRangeItem.listFromJson(json[r'limits']), 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiCoreV1LimitRangeSpec.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiCoreV1LimitRangeSpec.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiCoreV1LimitRangeSpec-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiCoreV1LimitRangeSpec.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | 'limits', 108 | }; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_core_v1_sysctl.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiCoreV1Sysctl { 14 | /// Returns a new [IoK8sApiCoreV1Sysctl] instance. 15 | IoK8sApiCoreV1Sysctl({ 16 | required this.name, 17 | required this.value, 18 | }); 19 | 20 | /// Name of a property to set 21 | String name; 22 | 23 | /// Value of a property to set 24 | String value; 25 | 26 | @override 27 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiCoreV1Sysctl && 28 | other.name == name && 29 | other.value == value; 30 | 31 | @override 32 | int get hashCode => 33 | // ignore: unnecessary_parenthesis 34 | (name.hashCode) + 35 | (value.hashCode); 36 | 37 | @override 38 | String toString() => 'IoK8sApiCoreV1Sysctl[name=$name, value=$value]'; 39 | 40 | Map toJson() { 41 | final json = {}; 42 | json[r'name'] = this.name; 43 | json[r'value'] = this.value; 44 | return json; 45 | } 46 | 47 | /// Returns a new [IoK8sApiCoreV1Sysctl] instance and imports its values from 48 | /// [value] if it's a [Map], null otherwise. 49 | // ignore: prefer_constructors_over_static_methods 50 | static IoK8sApiCoreV1Sysctl? fromJson(dynamic value) { 51 | if (value is Map) { 52 | final json = value.cast(); 53 | 54 | // Ensure that the map contains the required keys. 55 | // Note 1: the values aren't checked for validity beyond being non-null. 56 | // Note 2: this code is stripped in release mode! 57 | assert(() { 58 | requiredKeys.forEach((key) { 59 | assert(json.containsKey(key), 'Required key "IoK8sApiCoreV1Sysctl[$key]" is missing from JSON.'); 60 | assert(json[key] != null, 'Required key "IoK8sApiCoreV1Sysctl[$key]" has a null value in JSON.'); 61 | }); 62 | return true; 63 | }()); 64 | 65 | return IoK8sApiCoreV1Sysctl( 66 | name: mapValueOfType(json, r'name')!, 67 | value: mapValueOfType(json, r'value')!, 68 | ); 69 | } 70 | return null; 71 | } 72 | 73 | static List listFromJson(dynamic json, {bool growable = false,}) { 74 | final result = []; 75 | if (json is List && json.isNotEmpty) { 76 | for (final row in json) { 77 | final value = IoK8sApiCoreV1Sysctl.fromJson(row); 78 | if (value != null) { 79 | result.add(value); 80 | } 81 | } 82 | } 83 | return result.toList(growable: growable); 84 | } 85 | 86 | static Map mapFromJson(dynamic json) { 87 | final map = {}; 88 | if (json is Map && json.isNotEmpty) { 89 | json = json.cast(); // ignore: parameter_assignments 90 | for (final entry in json.entries) { 91 | final value = IoK8sApiCoreV1Sysctl.fromJson(entry.value); 92 | if (value != null) { 93 | map[entry.key] = value; 94 | } 95 | } 96 | } 97 | return map; 98 | } 99 | 100 | // maps a json object with a list of IoK8sApiCoreV1Sysctl-objects as value to a dart map 101 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 102 | final map = >{}; 103 | if (json is Map && json.isNotEmpty) { 104 | // ignore: parameter_assignments 105 | json = json.cast(); 106 | for (final entry in json.entries) { 107 | map[entry.key] = IoK8sApiCoreV1Sysctl.listFromJson(entry.value, growable: growable,); 108 | } 109 | } 110 | return map; 111 | } 112 | 113 | /// The list of required keys that must be present in a JSON. 114 | static const requiredKeys = { 115 | 'name', 116 | 'value', 117 | }; 118 | } 119 | 120 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_core_v1_pod_os.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiCoreV1PodOS { 14 | /// Returns a new [IoK8sApiCoreV1PodOS] instance. 15 | IoK8sApiCoreV1PodOS({ 16 | required this.name, 17 | }); 18 | 19 | /// Name is the name of the operating system. The currently supported values are linux and windows. Additional value may be defined in future and can be one of: https://github.com/opencontainers/runtime-spec/blob/master/config.md#platform-specific-configuration Clients should expect to handle additional values and treat unrecognized values in this field as os: null 20 | String name; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiCoreV1PodOS && 24 | other.name == name; 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (name.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiCoreV1PodOS[name=$name]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'name'] = this.name; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiCoreV1PodOS] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiCoreV1PodOS? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiCoreV1PodOS[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiCoreV1PodOS[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiCoreV1PodOS( 59 | name: mapValueOfType(json, r'name')!, 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiCoreV1PodOS.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiCoreV1PodOS.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiCoreV1PodOS-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiCoreV1PodOS.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | 'name', 108 | }; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_core_v1_http_header.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiCoreV1HTTPHeader { 14 | /// Returns a new [IoK8sApiCoreV1HTTPHeader] instance. 15 | IoK8sApiCoreV1HTTPHeader({ 16 | required this.name, 17 | required this.value, 18 | }); 19 | 20 | /// The header field name 21 | String name; 22 | 23 | /// The header field value 24 | String value; 25 | 26 | @override 27 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiCoreV1HTTPHeader && 28 | other.name == name && 29 | other.value == value; 30 | 31 | @override 32 | int get hashCode => 33 | // ignore: unnecessary_parenthesis 34 | (name.hashCode) + 35 | (value.hashCode); 36 | 37 | @override 38 | String toString() => 'IoK8sApiCoreV1HTTPHeader[name=$name, value=$value]'; 39 | 40 | Map toJson() { 41 | final json = {}; 42 | json[r'name'] = this.name; 43 | json[r'value'] = this.value; 44 | return json; 45 | } 46 | 47 | /// Returns a new [IoK8sApiCoreV1HTTPHeader] instance and imports its values from 48 | /// [value] if it's a [Map], null otherwise. 49 | // ignore: prefer_constructors_over_static_methods 50 | static IoK8sApiCoreV1HTTPHeader? fromJson(dynamic value) { 51 | if (value is Map) { 52 | final json = value.cast(); 53 | 54 | // Ensure that the map contains the required keys. 55 | // Note 1: the values aren't checked for validity beyond being non-null. 56 | // Note 2: this code is stripped in release mode! 57 | assert(() { 58 | requiredKeys.forEach((key) { 59 | assert(json.containsKey(key), 'Required key "IoK8sApiCoreV1HTTPHeader[$key]" is missing from JSON.'); 60 | assert(json[key] != null, 'Required key "IoK8sApiCoreV1HTTPHeader[$key]" has a null value in JSON.'); 61 | }); 62 | return true; 63 | }()); 64 | 65 | return IoK8sApiCoreV1HTTPHeader( 66 | name: mapValueOfType(json, r'name')!, 67 | value: mapValueOfType(json, r'value')!, 68 | ); 69 | } 70 | return null; 71 | } 72 | 73 | static List listFromJson(dynamic json, {bool growable = false,}) { 74 | final result = []; 75 | if (json is List && json.isNotEmpty) { 76 | for (final row in json) { 77 | final value = IoK8sApiCoreV1HTTPHeader.fromJson(row); 78 | if (value != null) { 79 | result.add(value); 80 | } 81 | } 82 | } 83 | return result.toList(growable: growable); 84 | } 85 | 86 | static Map mapFromJson(dynamic json) { 87 | final map = {}; 88 | if (json is Map && json.isNotEmpty) { 89 | json = json.cast(); // ignore: parameter_assignments 90 | for (final entry in json.entries) { 91 | final value = IoK8sApiCoreV1HTTPHeader.fromJson(entry.value); 92 | if (value != null) { 93 | map[entry.key] = value; 94 | } 95 | } 96 | } 97 | return map; 98 | } 99 | 100 | // maps a json object with a list of IoK8sApiCoreV1HTTPHeader-objects as value to a dart map 101 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 102 | final map = >{}; 103 | if (json is Map && json.isNotEmpty) { 104 | // ignore: parameter_assignments 105 | json = json.cast(); 106 | for (final entry in json.entries) { 107 | map[entry.key] = IoK8sApiCoreV1HTTPHeader.listFromJson(entry.value, growable: growable,); 108 | } 109 | } 110 | return map; 111 | } 112 | 113 | /// The list of required keys that must be present in a JSON. 114 | static const requiredKeys = { 115 | 'name', 116 | 'value', 117 | }; 118 | } 119 | 120 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_core_v1_pod_readiness_gate.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiCoreV1PodReadinessGate { 14 | /// Returns a new [IoK8sApiCoreV1PodReadinessGate] instance. 15 | IoK8sApiCoreV1PodReadinessGate({ 16 | required this.conditionType, 17 | }); 18 | 19 | /// ConditionType refers to a condition in the pod's condition list with matching type. 20 | String conditionType; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiCoreV1PodReadinessGate && 24 | other.conditionType == conditionType; 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (conditionType.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiCoreV1PodReadinessGate[conditionType=$conditionType]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'conditionType'] = this.conditionType; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiCoreV1PodReadinessGate] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiCoreV1PodReadinessGate? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiCoreV1PodReadinessGate[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiCoreV1PodReadinessGate[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiCoreV1PodReadinessGate( 59 | conditionType: mapValueOfType(json, r'conditionType')!, 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiCoreV1PodReadinessGate.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiCoreV1PodReadinessGate.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiCoreV1PodReadinessGate-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiCoreV1PodReadinessGate.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | 'conditionType', 108 | }; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_core_v1_node_selector.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiCoreV1NodeSelector { 14 | /// Returns a new [IoK8sApiCoreV1NodeSelector] instance. 15 | IoK8sApiCoreV1NodeSelector({ 16 | this.nodeSelectorTerms = const [], 17 | }); 18 | 19 | /// Required. A list of node selector terms. The terms are ORed. 20 | List nodeSelectorTerms; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiCoreV1NodeSelector && 24 | _deepEquality.equals(other.nodeSelectorTerms, nodeSelectorTerms); 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (nodeSelectorTerms.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiCoreV1NodeSelector[nodeSelectorTerms=$nodeSelectorTerms]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'nodeSelectorTerms'] = this.nodeSelectorTerms; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiCoreV1NodeSelector] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiCoreV1NodeSelector? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiCoreV1NodeSelector[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiCoreV1NodeSelector[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiCoreV1NodeSelector( 59 | nodeSelectorTerms: IoK8sApiCoreV1NodeSelectorTerm.listFromJson(json[r'nodeSelectorTerms']), 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiCoreV1NodeSelector.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiCoreV1NodeSelector.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiCoreV1NodeSelector-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiCoreV1NodeSelector.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | 'nodeSelectorTerms', 108 | }; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_storage_v1_csi_node_spec.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiStorageV1CSINodeSpec { 14 | /// Returns a new [IoK8sApiStorageV1CSINodeSpec] instance. 15 | IoK8sApiStorageV1CSINodeSpec({ 16 | this.drivers = const [], 17 | }); 18 | 19 | /// drivers is a list of information of all CSI Drivers existing on a node. If all drivers in the list are uninstalled, this can become empty. 20 | List drivers; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiStorageV1CSINodeSpec && 24 | _deepEquality.equals(other.drivers, drivers); 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (drivers.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiStorageV1CSINodeSpec[drivers=$drivers]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'drivers'] = this.drivers; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiStorageV1CSINodeSpec] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiStorageV1CSINodeSpec? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiStorageV1CSINodeSpec[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiStorageV1CSINodeSpec[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiStorageV1CSINodeSpec( 59 | drivers: IoK8sApiStorageV1CSINodeDriver.listFromJson(json[r'drivers']), 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiStorageV1CSINodeSpec.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiStorageV1CSINodeSpec.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiStorageV1CSINodeSpec-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiStorageV1CSINodeSpec.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | 'drivers', 108 | }; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /lib/dao/kube.dart: -------------------------------------------------------------------------------- 1 | import 'package:k8zdev/dao/dao.dart'; 2 | 3 | String clustersTable = "clusters"; 4 | String sqlCreateKubeClustersTable = ''' 5 | CREATE TABLE IF NOT EXISTS "$clustersTable" ( 6 | "id" INTEGER PRIMARY KEY AUTOINCREMENT, 7 | "name" TEXT NOT NULL, 8 | "server" TEXT NOT NULL, 9 | "ca" TEXT TEXT NOT NULL, 10 | "namespace" TEXT DEFAULT "default", 11 | "insecure" INTEGER NOT NULL DEFAULT 0, 12 | "client_key" TEXT NOT NULL DEFAULT "", 13 | "client_cert" TEXT NOT NULL DEFAULT "", 14 | "username" TEXT NOT NULL DEFAULT "", 15 | "password" TEXT NOT NULL DEFAULT "", 16 | "token" TEXT NOT NULL DEFAULT "", 17 | "created_at" INTEGER NOT NULL DEFAULT 0, 18 | "deleted" INTEGER NOT NULL DEFAULT 0 19 | ) 20 | '''; 21 | 22 | class K8zCluster { 23 | late int? id; 24 | String name; 25 | String server; 26 | String caData; 27 | String namespace; 28 | bool insecure; 29 | String clientKey; 30 | String clientCert; 31 | String username; 32 | String password; 33 | String token; 34 | int createdAt; 35 | bool? deleted; 36 | 37 | Map toJson() { 38 | return { 39 | 'id': id, 40 | 'name': name, 41 | 'server': server, 42 | 'ca': caData, 43 | 'namespace': namespace, 44 | 'insecure': insecure ? 1 : 0, 45 | 'client_key': clientKey, 46 | 'client_cert': clientCert, 47 | 'username': username, 48 | 'password': password, 49 | 'token': token, 50 | 'created_at': createdAt, 51 | }; 52 | } 53 | 54 | K8zCluster.fromJson(Map json) 55 | : id = json["id"], 56 | name = json["name"], 57 | server = json["server"], 58 | caData = json["ca"], 59 | namespace = json["namespace"], 60 | insecure = json["insecure"] == 1, 61 | clientKey = json["client_key"], 62 | clientCert = json["client_cert"], 63 | password = json["password"], 64 | username = json["username"], 65 | token = json["token"], 66 | createdAt = json["created_at"]; 67 | 68 | K8zCluster({ 69 | this.id, 70 | this.deleted, 71 | required this.name, 72 | required this.server, 73 | required this.caData, 74 | required this.namespace, 75 | required this.insecure, 76 | required this.clientKey, 77 | required this.clientCert, 78 | required this.username, 79 | required this.password, 80 | required this.token, 81 | required this.createdAt, 82 | }); 83 | 84 | static Future insert(K8zCluster config) async { 85 | try { 86 | config.id = await database.insert(clustersTable, config.toJson()); 87 | return config; 88 | } catch (e) { 89 | rethrow; 90 | } 91 | } 92 | 93 | static Future> batchInsert(List clusters) async { 94 | try { 95 | var batch = database.batch(); 96 | for (var cluster in clusters) { 97 | await database.insert(clustersTable, cluster.toJson()); 98 | } 99 | return await batch.commit(); 100 | } catch (e) { 101 | rethrow; 102 | } 103 | } 104 | 105 | static Future update(K8zCluster config) async { 106 | return await database.update(clustersTable, config.toJson(), 107 | where: 'id = ?', whereArgs: [config.id]); 108 | } 109 | 110 | static Future delete(K8zCluster config) async { 111 | return await database 112 | .delete(clustersTable, where: 'id = ?', whereArgs: [config.id]); 113 | } 114 | 115 | static Future> list() async { 116 | final List> maps = 117 | await database.query(clustersTable, where: 'deleted = 0'); 118 | return List.generate(maps.length, (i) { 119 | return K8zCluster( 120 | id: maps[i]['id'], 121 | name: maps[i]['name'], 122 | server: maps[i]['server'], 123 | caData: maps[i]['ca'], 124 | namespace: maps[i]['namespace'], 125 | insecure: maps[i]['insecure'] == 1, 126 | clientKey: maps[i]['client_key'], 127 | clientCert: maps[i]['client_cert'], 128 | username: maps[i]['username'], 129 | password: maps[i]['password'], 130 | token: maps[i]['token'], 131 | createdAt: maps[i]['created_at'], 132 | ); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_core_v1_scope_selector.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiCoreV1ScopeSelector { 14 | /// Returns a new [IoK8sApiCoreV1ScopeSelector] instance. 15 | IoK8sApiCoreV1ScopeSelector({ 16 | this.matchExpressions = const [], 17 | }); 18 | 19 | /// A list of scope selector requirements by scope of the resources. 20 | List matchExpressions; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiCoreV1ScopeSelector && 24 | _deepEquality.equals(other.matchExpressions, matchExpressions); 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (matchExpressions.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiCoreV1ScopeSelector[matchExpressions=$matchExpressions]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'matchExpressions'] = this.matchExpressions; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiCoreV1ScopeSelector] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiCoreV1ScopeSelector? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiCoreV1ScopeSelector[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiCoreV1ScopeSelector[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiCoreV1ScopeSelector( 59 | matchExpressions: IoK8sApiCoreV1ScopedResourceSelectorRequirement.listFromJson(json[r'matchExpressions']), 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiCoreV1ScopeSelector.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiCoreV1ScopeSelector.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiCoreV1ScopeSelector-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiCoreV1ScopeSelector.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | }; 108 | } 109 | 110 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 44 | 50 | 51 | 52 | 53 | 54 | 66 | 68 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_core_v1_downward_api_projection.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiCoreV1DownwardAPIProjection { 14 | /// Returns a new [IoK8sApiCoreV1DownwardAPIProjection] instance. 15 | IoK8sApiCoreV1DownwardAPIProjection({ 16 | this.items = const [], 17 | }); 18 | 19 | /// Items is a list of DownwardAPIVolume file 20 | List items; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiCoreV1DownwardAPIProjection && 24 | _deepEquality.equals(other.items, items); 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (items.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiCoreV1DownwardAPIProjection[items=$items]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'items'] = this.items; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiCoreV1DownwardAPIProjection] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiCoreV1DownwardAPIProjection? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiCoreV1DownwardAPIProjection[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiCoreV1DownwardAPIProjection[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiCoreV1DownwardAPIProjection( 59 | items: IoK8sApiCoreV1DownwardAPIVolumeFile.listFromJson(json[r'items']), 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiCoreV1DownwardAPIProjection.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiCoreV1DownwardAPIProjection.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiCoreV1DownwardAPIProjection-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiCoreV1DownwardAPIProjection.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | }; 108 | } 109 | 110 | -------------------------------------------------------------------------------- /lib/models/kubernetes/io_k8s_api_flowcontrol_v1beta1_user_subject.dart: -------------------------------------------------------------------------------- 1 | // 2 | // AUTO-GENERATED FILE, DO NOT MODIFY! 3 | // 4 | // @dart=2.12 5 | 6 | // ignore_for_file: unused_element, unused_import 7 | // ignore_for_file: always_put_required_named_parameters_first 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: lines_longer_than_80_chars 10 | 11 | part of models.k8s; 12 | 13 | class IoK8sApiFlowcontrolV1beta1UserSubject { 14 | /// Returns a new [IoK8sApiFlowcontrolV1beta1UserSubject] instance. 15 | IoK8sApiFlowcontrolV1beta1UserSubject({ 16 | required this.name, 17 | }); 18 | 19 | /// `name` is the username that matches, or \"*\" to match all usernames. Required. 20 | String name; 21 | 22 | @override 23 | bool operator ==(Object other) => identical(this, other) || other is IoK8sApiFlowcontrolV1beta1UserSubject && 24 | other.name == name; 25 | 26 | @override 27 | int get hashCode => 28 | // ignore: unnecessary_parenthesis 29 | (name.hashCode); 30 | 31 | @override 32 | String toString() => 'IoK8sApiFlowcontrolV1beta1UserSubject[name=$name]'; 33 | 34 | Map toJson() { 35 | final json = {}; 36 | json[r'name'] = this.name; 37 | return json; 38 | } 39 | 40 | /// Returns a new [IoK8sApiFlowcontrolV1beta1UserSubject] instance and imports its values from 41 | /// [value] if it's a [Map], null otherwise. 42 | // ignore: prefer_constructors_over_static_methods 43 | static IoK8sApiFlowcontrolV1beta1UserSubject? fromJson(dynamic value) { 44 | if (value is Map) { 45 | final json = value.cast(); 46 | 47 | // Ensure that the map contains the required keys. 48 | // Note 1: the values aren't checked for validity beyond being non-null. 49 | // Note 2: this code is stripped in release mode! 50 | assert(() { 51 | requiredKeys.forEach((key) { 52 | assert(json.containsKey(key), 'Required key "IoK8sApiFlowcontrolV1beta1UserSubject[$key]" is missing from JSON.'); 53 | assert(json[key] != null, 'Required key "IoK8sApiFlowcontrolV1beta1UserSubject[$key]" has a null value in JSON.'); 54 | }); 55 | return true; 56 | }()); 57 | 58 | return IoK8sApiFlowcontrolV1beta1UserSubject( 59 | name: mapValueOfType(json, r'name')!, 60 | ); 61 | } 62 | return null; 63 | } 64 | 65 | static List listFromJson(dynamic json, {bool growable = false,}) { 66 | final result = []; 67 | if (json is List && json.isNotEmpty) { 68 | for (final row in json) { 69 | final value = IoK8sApiFlowcontrolV1beta1UserSubject.fromJson(row); 70 | if (value != null) { 71 | result.add(value); 72 | } 73 | } 74 | } 75 | return result.toList(growable: growable); 76 | } 77 | 78 | static Map mapFromJson(dynamic json) { 79 | final map = {}; 80 | if (json is Map && json.isNotEmpty) { 81 | json = json.cast(); // ignore: parameter_assignments 82 | for (final entry in json.entries) { 83 | final value = IoK8sApiFlowcontrolV1beta1UserSubject.fromJson(entry.value); 84 | if (value != null) { 85 | map[entry.key] = value; 86 | } 87 | } 88 | } 89 | return map; 90 | } 91 | 92 | // maps a json object with a list of IoK8sApiFlowcontrolV1beta1UserSubject-objects as value to a dart map 93 | static Map> mapListFromJson(dynamic json, {bool growable = false,}) { 94 | final map = >{}; 95 | if (json is Map && json.isNotEmpty) { 96 | // ignore: parameter_assignments 97 | json = json.cast(); 98 | for (final entry in json.entries) { 99 | map[entry.key] = IoK8sApiFlowcontrolV1beta1UserSubject.listFromJson(entry.value, growable: growable,); 100 | } 101 | } 102 | return map; 103 | } 104 | 105 | /// The list of required keys that must be present in a JSON. 106 | static const requiredKeys = { 107 | 'name', 108 | }; 109 | } 110 | 111 | --------------------------------------------------------------------------------