├── kube
├── hpa
├── eksctl
├── pvc.yml
├── pv.yml
├── deployment.yml
├── cronjob.yml
├── cronjobfail.yml
└── job.yml
├── K8sCat
├── yaml
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── robots.txt
│ ├── asset-manifest.json
│ ├── manifest.json
│ ├── index.html
│ └── static
│ │ ├── css
│ │ ├── main.a6b6c53c.css
│ │ └── main.a6b6c53c.css.map
│ │ └── js
│ │ ├── main.e481028a.js.LICENSE.txt
│ │ ├── 787.eb3a1d75.chunk.js
│ │ └── 787.eb3a1d75.chunk.js.map
├── Assets.xcassets
│ ├── Contents.json
│ ├── aws.imageset
│ │ ├── aws.png
│ │ ├── aws@2x.png
│ │ ├── aws@3x.png
│ │ └── Contents.json
│ ├── aliyun.imageset
│ │ ├── aliyun.png
│ │ ├── aliyun@2x.png
│ │ ├── aliyun@3x.png
│ │ └── Contents.json
│ ├── config.imageset
│ │ ├── config.png
│ │ ├── config@2x.png
│ │ ├── config@3x.png
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── icon-1024.png
│ │ ├── icon-29.png
│ │ ├── icon-40.png
│ │ ├── icon-76.png
│ │ ├── icon-20@2x.png
│ │ ├── icon-20@3x.png
│ │ ├── icon-29@2x.png
│ │ ├── icon-29@3x.png
│ │ ├── icon-40@2x.png
│ │ ├── icon-40@3x.png
│ │ ├── icon-60@2x.png
│ │ ├── icon-60@3x.png
│ │ ├── icon-76@2x.png
│ │ ├── icon-20-ipad.png
│ │ ├── icon-29-ipad.png
│ │ ├── icon-83.5@2x.png
│ │ ├── icon-20@2x-ipad.png
│ │ ├── icon-29@2x-ipad.png
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── id
│ ├── AWSResign.swift
│ ├── Identifier.swift
│ ├── MyAWSClient.swift
│ └── Utils.swift
├── K8sCat.entitlements
├── inner
│ ├── CaptionText.swift
│ ├── LogView.swift
│ ├── ExecWebView.swift
│ ├── PVCView.swift
│ ├── YamlWebView.swift
│ ├── ContainerView.swift
│ ├── SecretView.swift
│ ├── ConfigMapView.swift
│ ├── HpaView.swift
│ ├── PVView.swift
│ ├── JobView.swift
│ ├── ReplicaView.swift
│ ├── NodeView.swift
│ ├── CronJobView.swift
│ ├── ServiceView.swift
│ ├── DaemonView.swift
│ ├── StatefulView.swift
│ ├── PodView.swift
│ └── DeploymentView.swift
├── K8sCatApp.swift
├── Model.xcdatamodeld
│ └── Model.xcdatamodel
│ │ └── contents
├── Model+Scale.swift
├── cluster
│ ├── NewClusterView.swift
│ ├── ClusterTypeView.swift
│ ├── ConfigView.swift
│ ├── ClusterView.swift
│ └── AWSView.swift
├── Core.xcdatamodeld
│ └── Core.xcdatamodel
│ │ └── contents
├── ContentView.swift
├── SettingView.swift
├── Persistence.swift
├── GlobalView.swift
├── StorageView.swift
├── tab
│ └── TabBarButton.swift
├── Ex.swift
├── Model+Controller.swift
└── Model+Yaml.swift
├── K8sCat.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ └── guyanhua.xcuserdatad
│ │ │ ├── IDEFindNavigatorScopes.plist
│ │ │ └── xcdebugger
│ │ │ └── Expressions.xcexplist
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── xcuserdata
│ └── guyanhua.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── xcshareddata
│ └── xcschemes
│ └── K8sCat.xcscheme
├── K8sCatUITests
├── K8sCatUITestsLaunchTests.swift
└── K8sCatUITests.swift
├── PrivacyPolicy.md
├── README.md
└── K8sCatTests
└── K8sCatTests.swift
/kube/hpa:
--------------------------------------------------------------------------------
1 | kubectl autoscale deployment hpa-demo --cpu-percent=10 --min=1 --max=10
--------------------------------------------------------------------------------
/K8sCat/yaml/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/yaml/favicon.ico
--------------------------------------------------------------------------------
/K8sCat/yaml/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/yaml/logo192.png
--------------------------------------------------------------------------------
/K8sCat/yaml/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/yaml/logo512.png
--------------------------------------------------------------------------------
/K8sCat/yaml/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/kube/eksctl:
--------------------------------------------------------------------------------
1 | eksctl create cluster --name demo-cat --region us-west-1
2 | eksctl delete cluster --name demo-cat --region us-west-1
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/aws.imageset/aws.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/aws.imageset/aws.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/aws.imageset/aws@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/aws.imageset/aws@2x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/aws.imageset/aws@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/aws.imageset/aws@3x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/aliyun.imageset/aliyun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/aliyun.imageset/aliyun.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/config.imageset/config.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/config.imageset/config.png
--------------------------------------------------------------------------------
/K8sCat/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/aliyun.imageset/aliyun@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/aliyun.imageset/aliyun@2x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/aliyun.imageset/aliyun@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/aliyun.imageset/aliyun@3x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/config.imageset/config@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/config.imageset/config@2x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/config.imageset/config@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/config.imageset/config@3x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-1024.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-29.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-40.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-76.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buhe/KubeCat/HEAD/K8sCat/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png
--------------------------------------------------------------------------------
/K8sCat.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/kube/pvc.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: PersistentVolumeClaim
3 | metadata:
4 | name: default
5 | spec:
6 | accessModes:
7 | - ReadWriteMany
8 | storageClassName: manual
9 | resources:
10 | requests:
11 | storage: 1Gi
12 |
--------------------------------------------------------------------------------
/kube/pv.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: PersistentVolume
3 | metadata:
4 | name: default
5 | spec:
6 | storageClassName: manual
7 | capacity:
8 | storage: 1Gi
9 | accessModes:
10 | - ReadWriteMany
11 | nfs:
12 | server: 10.244.1.4
13 | path: "/"
14 |
15 |
--------------------------------------------------------------------------------
/K8sCat/id/AWSResign.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AWSResign.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2023/1/8.
6 | //
7 |
8 | import Foundation
9 |
10 | struct AWSResign: Identifiable {
11 | let id: String
12 | let render: String
13 | let value: String
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/K8sCat.xcodeproj/project.xcworkspace/xcuserdata/guyanhua.xcuserdatad/IDEFindNavigatorScopes.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/K8sCat.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/kube/deployment.yml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: hpa-demo
5 | spec:
6 | selector:
7 | matchLabels:
8 | app: nginx
9 | template:
10 | metadata:
11 | labels:
12 | app: nginx
13 | spec:
14 | containers:
15 | - name: nginx
16 | image: nginx
17 | ports:
18 | - containerPort: 80
--------------------------------------------------------------------------------
/K8sCat/K8sCat.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/kube/cronjob.yml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: CronJob
3 | metadata:
4 | name: simplecronjob
5 | spec:
6 | # Run every 10 minutes. See https://en.wikipedia.org/wiki/Cron
7 | schedule: "*/10 * * * *"
8 | jobTemplate:
9 | spec:
10 | template:
11 | spec:
12 | containers:
13 | - name: simplepyscriptapp
14 | image: bmalevich/simplepyscriptapp
15 | restartPolicy: OnFailure
--------------------------------------------------------------------------------
/kube/cronjobfail.yml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: CronJob
3 | metadata:
4 | name: simplecronjobfail
5 | spec:
6 | # Run every 10 minutes. See https://en.wikipedia.org/wiki/Cron
7 | schedule: "*/10 * * * *"
8 | jobTemplate:
9 | spec:
10 | template:
11 | spec:
12 | containers:
13 | - name: simplepyscriptapp
14 | image: bmalevich/simplepyscriptappfail
15 | restartPolicy: OnFailure
--------------------------------------------------------------------------------
/K8sCat/inner/CaptionText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CaptionText.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/27.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CaptionText: View {
11 | let text: String
12 | var body: some View {
13 | Text(text).font(.caption)
14 | }
15 | }
16 |
17 | struct CaptionText_Previews: PreviewProvider {
18 | static var previews: some View {
19 | CaptionText(text: "123")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/aws.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "aws.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "aws@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "aws@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/kube/job.yml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: kubernetes-job-example
5 | labels:
6 | jobgroup: jobexample
7 | spec:
8 | template:
9 | metadata:
10 | name: kubejob
11 | labels:
12 | jobgroup: jobexample
13 | spec:
14 | containers:
15 | - name: c
16 | image: devopscube/kubernetes-job-demo:latest
17 | args: ["100"]
18 | restartPolicy: OnFailure
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/aliyun.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "aliyun.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "aliyun@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "aliyun@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/config.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "config.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "config@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "config@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/K8sCat/K8sCatApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // K8sCatApp.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/17.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct K8sCatApp: App {
12 | let persistenceController = PersistenceController.shared
13 | var body: some Scene {
14 | WindowGroup {
15 | ContentView(viewModel: ViewModel(viewContext: persistenceController.container.viewContext))
16 | .environment(\.managedObjectContext, persistenceController.container.viewContext)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/K8sCat/Model.xcdatamodeld/Model.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/K8sCat/yaml/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "main.css": "/static/css/main.a6b6c53c.css",
4 | "main.js": "/static/js/main.e481028a.js",
5 | "static/js/787.eb3a1d75.chunk.js": "/static/js/787.eb3a1d75.chunk.js",
6 | "index.html": "/index.html",
7 | "main.a6b6c53c.css.map": "/static/css/main.a6b6c53c.css.map",
8 | "main.e481028a.js.map": "/static/js/main.e481028a.js.map",
9 | "787.eb3a1d75.chunk.js.map": "/static/js/787.eb3a1d75.chunk.js.map"
10 | },
11 | "entrypoints": [
12 | "static/css/main.a6b6c53c.css",
13 | "static/js/main.e481028a.js"
14 | ]
15 | }
--------------------------------------------------------------------------------
/K8sCat/yaml/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/K8sCat/yaml/index.html:
--------------------------------------------------------------------------------
1 |
React App
2 |
--------------------------------------------------------------------------------
/K8sCat/Model+Scale.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Model+Scale.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2023/1/4.
6 | //
7 |
8 | import Foundation
9 | import SwiftkubeModel
10 |
11 | extension Model {
12 | func scaleDeployment(deployment: Deployment, replicas: Int32) async {
13 | if let client = client {
14 | if var scale = try? await client.appsV1.deployments.getScale(in: .namespace(deployment.namespace), name: deployment.name) {
15 | scale.spec?.replicas = replicas
16 | let _ = try? await client.appsV1.deployments.updateScale(in: .namespace(deployment.namespace), name: deployment.name, scale: scale)
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/K8sCat/yaml/static/css/main.a6b6c53c.css:
--------------------------------------------------------------------------------
1 | #root,.playground-container,body,html{background-color:#efefef;height:100vh;margin:0;padding:0}.playground-container{display:flex;flex-direction:column;max-height:100vh}.playground-panel{display:flex;flex:1 1;flex-direction:column}.cm-outer-container{flex:1 1}.cm-editor{flex:1 1;height:100%;position:relative}.cm-scroller{bottom:0;left:0;overflow-y:auto;position:absolute!important;right:0;top:0}.cm-content{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.btn-container{background:#fff}.btn-container,.btn-container-dark{align-items:center;display:flex;justify-content:center}.btn-container-dark{background:#1c1c1e}.btn{background:#fff}.btn,.btn-dark{border:hidden;color:#007aff;font-size:large;margin:15px;width:288px}.btn-dark{background:#1c1c1e}
2 | /*# sourceMappingURL=main.a6b6c53c.css.map*/
--------------------------------------------------------------------------------
/K8sCat.xcodeproj/xcuserdata/guyanhua.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | K8sCat.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 3FFCCF88294DFF6000FE6710
16 |
17 | primary
18 |
19 |
20 | 3FFCCF99294DFF6100FE6710
21 |
22 | primary
23 |
24 |
25 | 3FFCCFA3294DFF6100FE6710
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/K8sCatUITests/K8sCatUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // K8sCatUITestsLaunchTests.swift
3 | // K8sCatUITests
4 | //
5 | // Created by 顾艳华 on 2022/12/17.
6 | //
7 |
8 | import XCTest
9 |
10 | final class K8sCatUITestsLaunchTests: XCTestCase {
11 |
12 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
13 | true
14 | }
15 |
16 | override func setUpWithError() throws {
17 | continueAfterFailure = false
18 | }
19 |
20 | func testLaunch() throws {
21 | let app = XCUIApplication()
22 | app.launch()
23 |
24 | // Insert steps here to perform after app launch but before taking a screenshot,
25 | // such as logging into a test account or navigating somewhere in the app
26 |
27 | let attachment = XCTAttachment(screenshot: app.screenshot())
28 | attachment.name = "Launch Screen"
29 | attachment.lifetime = .keepAlways
30 | add(attachment)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/PrivacyPolicy.md:
--------------------------------------------------------------------------------
1 | ## Privacy Policy
2 |
3 | 1. We comply with the Act on the Protection of Personal Information and related laws and regulations in World and engage in the appropriate acquisition, use and provision of personal information in accordance with the below disclosed Handling of Personal Information and internal regulations, etc.
4 | 2. We strive to prevent personal information we handle from being leaked, lost, or damaged by taking necessary and proper security measures, and we take necessary corrective measures in the event that an incident occurs.
5 | 3. We clearly prescribe rules on the handling of personal information in internal regulations and thoroughly familiarize officers and employees with such rules. Moreover, when providing personal information to contractors we manage and supervise contractors to ensure they handle personal information properly.
6 | 4. A designated office accepts and appropriately responds to complaints, consultations and requests from individuals concerned for disclosure, amendment, suspension of use, etc. of personal information we retain.
7 |
--------------------------------------------------------------------------------
/K8sCat/yaml/static/js/main.e481028a.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /**
2 | * @license React
3 | * react-dom.production.min.js
4 | *
5 | * Copyright (c) Facebook, Inc. and its affiliates.
6 | *
7 | * This source code is licensed under the MIT license found in the
8 | * LICENSE file in the root directory of this source tree.
9 | */
10 |
11 | /**
12 | * @license React
13 | * react-jsx-runtime.production.min.js
14 | *
15 | * Copyright (c) Facebook, Inc. and its affiliates.
16 | *
17 | * This source code is licensed under the MIT license found in the
18 | * LICENSE file in the root directory of this source tree.
19 | */
20 |
21 | /**
22 | * @license React
23 | * react.production.min.js
24 | *
25 | * Copyright (c) Facebook, Inc. and its affiliates.
26 | *
27 | * This source code is licensed under the MIT license found in the
28 | * LICENSE file in the root directory of this source tree.
29 | */
30 |
31 | /**
32 | * @license React
33 | * scheduler.production.min.js
34 | *
35 | * Copyright (c) Facebook, Inc. and its affiliates.
36 | *
37 | * This source code is licensed under the MIT license found in the
38 | * LICENSE file in the root directory of this source tree.
39 | */
40 |
--------------------------------------------------------------------------------
/K8sCat/cluster/NewClusterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewClusterView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/31.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct NewClusterView: View {
11 | @Environment(\.managedObjectContext) private var viewContext
12 | let first: Bool
13 | let type: ClusterType
14 | let close: () -> Void
15 | var body: some View {
16 | switch type {
17 | case .Aliyun:
18 | ConfigView(first: first){
19 | close()
20 | }
21 | .environment(\.managedObjectContext, viewContext)
22 | case .AWS:
23 | AWSView(first: first){
24 | close()
25 | }
26 | .environment(\.managedObjectContext, viewContext)
27 | case .KubeConfig:
28 | ConfigView(first: first){
29 | close()
30 | }
31 | .environment(\.managedObjectContext, viewContext)
32 | default: EmptyView()
33 | }
34 | }
35 | }
36 |
37 | struct NewClusterView_Previews: PreviewProvider {
38 | static var previews: some View {
39 | NewClusterView(first: true, type: .Demo){}
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Kube Cat
2 |
3 | Kube Cat helps you easily manage Kubernetes clusters from your mobile device, allowing you to view cluster status and quickly resolve issues while you're on the go. Kube Cat has a lot of features and more to come!:
4 |
5 | - View the status of almost any resources. e.g.
6 | Pods, Deployments, PVs, PVCs, HPAs, Jobs, CronJobs, Nodes, Config Maps, Docker containers and so on.
7 | - Inspect logs of any containers in realtime!
8 | - View / Update almost any resources via YAML.
9 | - Support managing multiple clusters.
10 | - Supported authentication providers, Kube Config, AWS IAM Authenticator (EKS), Aliyun etc.
11 |
12 | Who need it, please compile it by yourself. If you find it troublesome or want to support me, go to https://apps.apple.com/us/app/kube-cat/id1665033555 , the price is 1.99 Dollar.
13 |
14 |       
15 |
--------------------------------------------------------------------------------
/K8sCat/Core.xcdatamodeld/Core.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/K8sCatTests/K8sCatTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // K8sCatTests.swift
3 | // K8sCatTests
4 | //
5 | // Created by 顾艳华 on 2022/12/17.
6 | //
7 |
8 | import XCTest
9 |
10 | final class K8sCatTests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 | }
15 |
16 | override func tearDownWithError() throws {
17 | // Put teardown code here. This method is called after the invocation of each test method in the class.
18 | }
19 |
20 | func testExample() throws {
21 | // This is an example of a functional test case.
22 | // Use XCTAssert and related functions to verify your tests produce the correct results.
23 | // Any test you write for XCTest can be annotated as throws and async.
24 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
25 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
26 | }
27 |
28 | func testPerformanceExample() throws {
29 | // This is an example of a performance test case.
30 | measure {
31 | // Put the code you want to measure the time of here.
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/K8sCat/inner/LogView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 3/4/24.
6 | //
7 |
8 | import SwiftUI
9 | import SwiftkubeClient
10 | import SwiftkubeModel
11 | import SwiftUIX
12 |
13 | struct LogView: View {
14 | var logTask: SwiftkubeClientTask?
15 | @State var search = ""
16 | @State var logsLines: [String] = []
17 |
18 | init(logTask: SwiftkubeClientTask?) {
19 | self.logTask = logTask
20 | }
21 | var body: some View {
22 | SearchBar(text: $search)
23 | .padding()
24 | List {
25 | ForEach(logsLines, id: \.self) {
26 | l in
27 | if l.contains(search) {
28 | Text(l)
29 | .foregroundColor(.systemYellow)
30 | } else {
31 | Text(l)
32 | }
33 |
34 | }
35 | }
36 | .onAppear {
37 | Task {
38 | if let logTask = logTask {
39 | for try await line in logTask.start() {
40 | // print(line)
41 | DispatchQueue.main.async {
42 | logsLines.append(line)
43 | }
44 | }
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
51 | //#Preview {
52 | // LogView()
53 | //}
54 |
--------------------------------------------------------------------------------
/K8sCatUITests/K8sCatUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // K8sCatUITests.swift
3 | // K8sCatUITests
4 | //
5 | // Created by 顾艳华 on 2022/12/17.
6 | //
7 |
8 | import XCTest
9 |
10 | final class K8sCatUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | func testExample() throws {
26 | // UI tests must launch the application that they test.
27 | let app = XCUIApplication()
28 | app.launch()
29 |
30 | // Use XCTAssert and related functions to verify your tests produce the correct results.
31 | }
32 |
33 | func testLaunchPerformance() throws {
34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
35 | // This measures how long it takes to launch your application.
36 | measure(metrics: [XCTApplicationLaunchMetric()]) {
37 | XCUIApplication().launch()
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/K8sCat/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/17.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ContentView: View {
11 | @ObservedObject var viewModel: ViewModel
12 | @Environment(\.managedObjectContext) private var viewContext
13 | var body: some View {
14 | TabView {
15 | NamespaceView(viewModel: viewModel)
16 | .environment(\.managedObjectContext, viewContext)
17 | .tabItem {
18 | Label("Payloads", systemImage: "aqi.medium")
19 | }
20 |
21 | StorageView(viewModel: viewModel)
22 | .environment(\.managedObjectContext, viewContext)
23 | .tabItem {
24 | Label("Storage", systemImage: "opticaldiscdrive")
25 | }
26 |
27 | GlobalView(viewModel: viewModel)
28 | .environment(\.managedObjectContext, viewContext)
29 | .tabItem {
30 | Label("Nodes", systemImage: "display.2")
31 | }
32 |
33 | SettingView(viewModel: viewModel)
34 | .environment(\.managedObjectContext, viewContext)
35 | .tabItem {
36 | Label("Setting", systemImage: "gear")
37 | }
38 | }
39 | }
40 | }
41 |
42 | struct ContentView_Previews: PreviewProvider {
43 | static var previews: some View {
44 | ContentView(viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/K8sCat.xcodeproj/project.xcworkspace/xcuserdata/guyanhua.xcuserdatad/xcdebugger/Expressions.xcexplist:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
8 |
10 |
11 |
12 |
13 |
15 |
16 |
18 |
19 |
20 |
21 |
23 |
24 |
26 |
27 |
29 |
30 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/K8sCat/inner/ExecWebView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebView.swift
3 | // Yamler
4 | //
5 | // Created by 顾艳华 on 2022/12/14.
6 | //
7 |
8 | import SwiftUI
9 | import WebKit
10 | import Foundation
11 | /*
12 | TODO
13 | */
14 | struct ExecWebView: UIViewRepresentable {
15 |
16 | @Environment(\.colorScheme) private var colorScheme
17 | // let pod: Pod
18 | // let container: Container
19 | let close: () -> Void
20 |
21 | func makeUIView(context: Context) -> WKWebView {
22 | let webView = WKWebView()
23 |
24 | let baseUrl = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "exec")!
25 | // print(baseUrl)
26 | var component = URLComponents(url: baseUrl, resolvingAgainstBaseURL: false)
27 | component?.queryItems = [URLQueryItem(name: "theme", value: colorScheme == .dark ? "dark" : "light")]
28 |
29 | if let url = component?.url {
30 | // print(url)
31 | webView.loadFileURL(url, allowingReadAccessTo: url)
32 | }
33 | return webView
34 | }
35 |
36 | func updateUIView(_ webView: WKWebView, context: Context) {
37 |
38 |
39 |
40 |
41 | // let baseUrl = URL(string: "http://localhost:3000")!
42 | // var component = URLComponents(url: baseUrl, resolvingAgainstBaseURL: false)
43 | // component?.queryItems = [URLQueryItem(name: "items", value: string)]
44 | // if let url = component?.url {
45 | // print(url)
46 | // webView.load(URLRequest(url: url))
47 | // }
48 |
49 | }
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/K8sCat/yaml/static/css/main.a6b6c53c.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"static/css/main.a6b6c53c.css","mappings":"AAAA,sCAOE,wBAAyB,CADzB,YAAa,CADb,QAAS,CADT,SAIF,CAEA,sBACE,YAAa,CACb,qBAAsB,CACtB,gBACF,CAEA,kBAEE,YAAa,CADb,QAAO,CAEP,qBACF,CAEA,oBACE,QACF,CAEA,WACE,QAAO,CACP,WAAY,CACZ,iBAEF,CAEA,aAIE,QAAS,CACT,MAAO,CACP,eAAgB,CALhB,2BAA6B,CAE7B,OAAQ,CADR,KAKF,CAEA,YACE,oBAAqB,CACrB,oBAAqB,CACrB,iBACF,CAEA,eAIE,eACF,CAEA,mCALE,kBAAmB,CADnB,YAAa,CAEb,sBASF,CALA,oBAIE,kBACF,CAEA,KAKE,eAEF,CAEA,eANE,aAAc,CACd,aAAc,CAEd,eAAgB,CALhB,WAAY,CACZ,WAcF,CAPA,UAKE,kBAEF","sources":["App.css"],"sourcesContent":["html,\nbody,\n#root,\n.playground-container {\n padding: 0;\n margin: 0;\n height: 100vh;\n background-color: #efefef;\n}\n\n.playground-container {\n display: flex;\n flex-direction: column;\n max-height: 100vh;\n}\n\n.playground-panel {\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.cm-outer-container {\n flex: 1;\n}\n\n.cm-editor {\n flex: 1;\n height: 100%;\n position: relative;\n /* <-- .cm-scroller absolute positioning depends on this*/\n}\n\n.cm-scroller {\n position: absolute !important;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n overflow-y: auto;\n}\n\n.cm-content {\n word-wrap: break-word;\n white-space: pre-wrap;\n word-break: normal;\n}\n\n.btn-container {\n display: flex;\n align-items: center;\n justify-content: center;\n background: white;\n}\n\n.btn-container-dark {\n display: flex;\n align-items: center;\n justify-content: center;\n background: #1C1C1E;\n}\n\n.btn {\n margin: 15px;\n width: 288px;\n border: hidden;\n color: #007AFF;\n background: white;\n font-size: large;\n}\n\n.btn-dark {\n margin: 15px;\n width: 288px;\n border: hidden;\n color: #007AFF;\n background: #1C1C1E;\n font-size: large;\n}"],"names":[],"sourceRoot":""}
--------------------------------------------------------------------------------
/K8sCat/SettingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/20.
6 | //
7 |
8 | import SwiftUI
9 | import StoreKit
10 |
11 | struct SettingView: View {
12 | let viewModel: ViewModel
13 | @Environment(\.managedObjectContext) private var viewContext
14 | @Environment(\.requestReview) var requestReview
15 |
16 | var body: some View {
17 | NavigationStack {
18 | Form{
19 | Section(){
20 | Button{
21 | #if os(iOS)
22 | if let url = URL(string: "https://github.com/buhe/KubeCat/issues") {
23 | UIApplication.shared.open(url)
24 | }
25 | #endif
26 | } label: {
27 |
28 | Text("Feedback")
29 |
30 | }.buttonStyle(PlainButtonStyle())
31 | Button{
32 | requestReview()
33 | } label: {
34 |
35 | Text("Rate")
36 |
37 | }.buttonStyle(PlainButtonStyle())
38 | HStack{
39 | Text("Version")
40 | Spacer()
41 | Text(Bundle.main.releaseVersionNumber ?? "")
42 | }
43 | HStack{
44 | Text("License")
45 | Spacer()
46 | Text("GPLv3")
47 | }
48 | }
49 |
50 |
51 | }
52 | }
53 | }
54 | }
55 |
56 | struct SettingView_Previews: PreviewProvider {
57 | static var previews: some View {
58 | SettingView(viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/K8sCat/inner/PVCView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PVCView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/29.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PVCView: View {
11 | let pvc: PersistentVolumeClaim
12 | let viewModel: ViewModel
13 |
14 | // @State var showYaml = false
15 | var body: some View {
16 | Form {
17 | Section(header: "Name") {
18 | Text(pvc.name)
19 | }
20 |
21 |
22 | }
23 | .navigationTitle("Persistent Volume Claim")
24 | // .toolbar{
25 | // Menu {
26 | // Button {
27 | // // do something
28 | // let yaml = pvc.encodeYaml(client: viewModel.model.client)
29 | // print("Yaml: \(yaml)")
30 | // showYaml = true
31 | //// deployment.decodeYaml(client: viewModel.model.client, yaml: yaml)
32 | // } label: {
33 | // Text("View/Edit Yaml")
34 | // Image(systemName: "note.text")
35 | // }
36 | // Button {
37 | // // do something
38 | // } label: {
39 | // Text("Delete Resource")
40 | // Image(systemName: "trash")
41 | // }
42 | // } label: {
43 | // Image(systemName: "ellipsis")
44 | // }
45 | // }.sheet(isPresented: $showYaml){
46 | // YamlWebView(yamlble: pvc, model: viewModel.model) {
47 | // showYaml = false
48 | // }
49 | // Button{
50 | // urlScheme(yamlble: pvc, client: viewModel.model.client)
51 | // }label: {
52 | // Text("Load yaml via Yamler")
53 | // }.padding()
54 | // }
55 | }
56 | }
57 |
58 | //struct PVCView_Previews: PreviewProvider {
59 | // static var previews: some View {
60 | // PVCView()
61 | // }
62 | //}
63 |
--------------------------------------------------------------------------------
/K8sCat/Persistence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Persistence.swift
3 | // Convict Conditioning Pro
4 | //
5 | // Created by 顾艳华 on 2022/12/23.
6 | //
7 |
8 | import CoreData
9 |
10 | struct PersistenceController {
11 | static let shared = PersistenceController()
12 |
13 | static var preview: PersistenceController = {
14 | let result = PersistenceController(inMemory: true)
15 | let viewContext = result.container.viewContext
16 | for _ in 0..<10 {
17 | let newItem = ClusterEntry(context: viewContext)
18 | newItem.config = """
19 |
20 | """
21 | }
22 | do {
23 | try viewContext.save()
24 | } catch {
25 | // Replace this implementation with code to handle the error appropriately.
26 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
27 | let nsError = error as NSError
28 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
29 | }
30 | return result
31 | }()
32 |
33 | let container: NSPersistentCloudKitContainer
34 |
35 | init(inMemory: Bool = false) {
36 | container = NSPersistentCloudKitContainer(name: "Core")
37 | if inMemory {
38 | container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
39 | }
40 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in
41 | if let error = error as NSError? {
42 | // Replace this implementation with code to handle the error appropriately.
43 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
44 |
45 | /*
46 | Typical reasons for an error here include:
47 | * The parent directory does not exist, cannot be created, or disallows writing.
48 | * The persistent store is not accessible, due to permissions or data protection when the device is locked.
49 | * The device is out of space.
50 | * The store could not be migrated to the current model version.
51 | Check the error message to determine what the actual problem was.
52 | */
53 | fatalError("Unresolved error \(error), \(error.userInfo)")
54 | }
55 | })
56 | container.viewContext.automaticallyMergesChangesFromParent = true
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/K8sCat/inner/YamlWebView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebView.swift
3 | // Yamler
4 | //
5 | // Created by 顾艳华 on 2022/12/14.
6 | //
7 |
8 | import SwiftUI
9 | import WebKit
10 | import Foundation
11 |
12 | class WKHandler: NSObject, WKScriptMessageHandler {
13 | let yamlble: Yamlble
14 | let model: Model
15 | var lastYaml: String
16 | let close: () -> Void
17 | init(yamlble: Yamlble, model: Model, lastYaml: String, close: @escaping () -> Void) {
18 | self.yamlble = yamlble
19 | self.model = model
20 | self.lastYaml = lastYaml
21 | self.close = close
22 | }
23 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
24 | guard let dict = message.body as? [String : AnyObject] else {
25 | return
26 | }
27 |
28 | print("from webview \(dict)")
29 | if dict["message"] != nil {
30 | lastYaml = dict["message"]! as! String
31 | }
32 | if dict["done"] != nil && dict["done"]! as! String == "true" {
33 | // download yaml
34 | print("load via yaml \(lastYaml)")
35 | Task {
36 | await yamlble.decodeYamlAndUpdate(client: model.client, yaml: lastYaml)
37 | }
38 | close()
39 | }
40 | }
41 | }
42 | struct YamlWebView: UIViewRepresentable {
43 |
44 | @Environment(\.colorScheme) private var colorScheme
45 | // let pod: Pod
46 | // let container: Container
47 | let yamlble: Yamlble
48 | let model: Model
49 | let close: () -> Void
50 |
51 | func makeUIView(context: Context) -> WKWebView {
52 | let webView = WKWebView()
53 | Task {
54 | let yaml = await yamlble.encodeYaml(client: model.client)
55 | webView.configuration.userContentController.add(WKHandler(yamlble: yamlble, model: model, lastYaml: yaml, close: close), name: "toggleMessageHandler")
56 | if let baseUrl = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "yaml") {
57 | // print(baseUrl)
58 | var component = URLComponents(url: baseUrl, resolvingAgainstBaseURL: false)
59 | component?.queryItems = [URLQueryItem(name: "items", value: yaml), URLQueryItem(name: "theme", value: colorScheme == .dark ? "dark" : "light")]
60 |
61 | if let url = component?.url {
62 | print(url)
63 | webView.loadFileURL(url, allowingReadAccessTo: url)
64 | }
65 |
66 | }
67 | }
68 | return webView
69 | }
70 |
71 | func updateUIView(_ webView: WKWebView, context: Context) {
72 |
73 | }
74 | }
75 |
76 |
--------------------------------------------------------------------------------
/K8sCat/cluster/ClusterTypeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClusterTypeView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/31.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ClusterTypeView: View {
11 | @Environment(\.managedObjectContext) private var viewContext
12 | @Environment(\.presentationMode) var presentationMode
13 | let first: Bool
14 | let close: () -> Void
15 | var body: some View {
16 | NavigationStack {
17 | List {
18 | ForEach(ClusterType.allCases, id: \.self) {
19 | c in
20 | switch c {
21 | case .KubeConfig:
22 | NavigationLink {
23 | NewClusterView(first: first, type: c){
24 | presentationMode.wrappedValue.dismiss()
25 | close()
26 | }
27 | .environment(\.managedObjectContext, viewContext)
28 | } label: {
29 | Image("config")
30 | Text(c.rawValue)
31 |
32 |
33 | }
34 | case .Aliyun:
35 | NavigationLink {
36 | NewClusterView(first: first, type: c){
37 | presentationMode.wrappedValue.dismiss()
38 | close()
39 | }
40 | .environment(\.managedObjectContext, viewContext)
41 | } label: {
42 | Image("aliyun")
43 | Text(c.rawValue)
44 |
45 |
46 | }
47 | case .AWS:
48 | NavigationLink {
49 | NewClusterView(first: first, type: c){
50 | presentationMode.wrappedValue.dismiss()
51 | close()
52 | }
53 | .environment(\.managedObjectContext, viewContext)
54 | } label: {
55 | Image("aws")
56 | Text(c.rawValue)
57 |
58 |
59 | }
60 | default: EmptyView()
61 | }
62 |
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
69 | struct ClusterTypeView_Previews: PreviewProvider {
70 | static var previews: some View {
71 | ClusterTypeView(first: true){}
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/K8sCat/GlobalView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GlobalView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/20.
6 | //
7 |
8 | import SwiftUI
9 | import SwiftUIX
10 |
11 | struct GlobalView: View {
12 | @State var search = ""
13 | @State var tabIndex = 0
14 | @ObservedObject var viewModel: ViewModel
15 | @State var showCluster = false
16 | @FetchRequest(
17 | sortDescriptors: [],
18 | animation: .default)
19 | private var cluters: FetchedResults
20 | @Environment(\.managedObjectContext) private var viewContext
21 |
22 | var body: some View {
23 | VStack {
24 | NavigationStack {
25 | HStack{
26 | SearchBar(text: $search).padding(.horizontal)
27 | Button{ showCluster = true }label: {
28 | Image(systemName: cluters.filter{$0.selected}.first?.icon ?? "0.circle")
29 | }.padding(.trailing)
30 | }
31 | NodesTabBar(tabIndex: $tabIndex).padding(.horizontal, 26)
32 | switch tabIndex {
33 | case 0:
34 | List {
35 | ForEach(viewModel.nodes.filter{$0.name.contains(search.lowercased()) || search == ""}) {
36 | i in
37 | NavigationLink {
38 | NodeView(node: i, viewModel: viewModel)
39 | } label: {
40 | Image(systemName: "display")
41 | VStack(alignment: .leading) {
42 | Text(i.name)
43 | CaptionText(text: i.version)
44 | }
45 | }
46 |
47 | }
48 | }.listStyle(PlainListStyle())
49 | .onAppear {
50 | Task {
51 | await viewModel.nodes()
52 | }
53 | }
54 | .refreshable {
55 | viewModel.model.nodes = nil
56 | Task {
57 | await viewModel.nodes()
58 | }
59 | }
60 | default:
61 | EmptyView()
62 | }
63 | }
64 | }.sheet(isPresented: $showCluster){
65 | ClusterView(viewModel: viewModel){
66 | showCluster = false
67 | }
68 | .environment(\.managedObjectContext, viewContext)
69 | }
70 | }
71 | }
72 |
73 | struct GlobalView_Previews: PreviewProvider {
74 | static var previews: some View {
75 | GlobalView(viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/K8sCat/inner/ContainerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContainerView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 | import SwiftkubeClient
10 | import SwiftkubeModel
11 | import SwiftUIX
12 |
13 | struct ContainerView: View {
14 | let pod: Pod
15 | let container: Container
16 | let viewModel: ViewModel
17 |
18 | @State var showLogs = false
19 | @State var showShell = false
20 |
21 |
22 | func logs() -> SwiftkubeClientTask? {
23 | viewModel.model.logs(in: .namespace(viewModel.ns), pod: pod, container: container)
24 | }
25 |
26 | var body: some View {
27 | Form {
28 | Section(header: "Name") {
29 | Text(container.name)
30 | }
31 | Section(header: "Status") {
32 | Text(container.status.rawValue)
33 | }
34 | Section(header: "Error") {
35 | Text(container.error ? "True" : "False")
36 | .foregroundColor(container.error ? .red : .none)
37 | }
38 | Section(header: "Ready") {
39 | Text(container.ready ? "True" : "False")
40 | .foregroundColor(container.ready ? .none : .yellow)
41 | }
42 | Section(header: "Image") {
43 | Text(container.image).font(.caption)
44 | }
45 | Section(header: "Termination Message") {
46 | HStack{
47 | Text("Path")
48 | Spacer()
49 | Text(container.path)
50 | }
51 | HStack{
52 | Text("Policy")
53 | Spacer()
54 | Text(container.policy)
55 | }
56 | }
57 |
58 | Section(header: "Misc") {
59 | HStack{
60 | Text("Pull Policy")
61 | Spacer()
62 | Text(container.pullPolicy)
63 | }
64 |
65 | }
66 | }
67 | .toolbar {
68 | // Button {
69 | // showShell = true
70 | // } label: {
71 | // Label("shell", systemImage: "terminal")
72 | // }
73 |
74 | Button {
75 | viewModel.logTask = logs()
76 | showLogs = true
77 |
78 | } label: {
79 | Label("log", systemImage: "doc.text.magnifyingglass")
80 | }
81 | }.sheet(isPresented: $showLogs, onDismiss: {
82 | viewModel.logTask?.cancel()
83 | }) {
84 | LogView(logTask: viewModel.logTask)
85 | }
86 | .sheet(isPresented: $showShell) {
87 | // WebView(pod: pod, container: container).padding()
88 | }
89 | .navigationTitle("Container")
90 | }
91 | }
92 |
93 | //struct ContainerView_Previews: PreviewProvider {
94 | // static var previews: some View {
95 | // ContainerView()
96 | // }
97 | //}
98 |
99 | enum ContainerStatus: String {
100 | case Running
101 | case Waiting
102 | case Terminated
103 | }
104 |
--------------------------------------------------------------------------------
/K8sCat/inner/SecretView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SecretView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SecretView: View {
11 | let secret: Secret
12 | let viewModel: ViewModel
13 | var body: some View {
14 | Form {
15 | Section(header: "Name") {
16 | Text(secret.name)
17 | }
18 | Section(header: "Data") {
19 |
20 | List {
21 | ForEach((secret.data ?? [:]).sorted(by: >), id: \.key) {
22 | key, value in
23 | NavigationLink {
24 | Form{
25 | Section(header: "Name"){
26 | Text(key)
27 | }
28 | Section(header: "Data"){
29 | Text(value)
30 | }
31 |
32 |
33 | }
34 | } label: {
35 | VStack(alignment: .leading) {
36 | Text(key)
37 | }
38 | }
39 | }
40 | }
41 |
42 |
43 |
44 | }
45 | Section(header: "Labels and Annotations") {
46 | NavigationLink {
47 | List {
48 | ForEach((secret.labels ?? [:]).sorted(by: >), id: \.key) {
49 | key, value in
50 | VStack(alignment: .leading) {
51 | Text(key)
52 |
53 | CaptionText(text: value)
54 | }
55 | }
56 | }
57 |
58 | } label: {
59 | Text("Labels")
60 | }
61 | NavigationLink {
62 | List {
63 | ForEach((secret.annotations ?? [:]).sorted(by: >), id: \.key) {
64 | key, value in
65 | VStack(alignment: .leading) {
66 | Text(key)
67 | CaptionText(text: value)
68 | }
69 | }
70 | }
71 | } label: {
72 | Text("Annotations")
73 | }
74 | }
75 |
76 | Section(header: "Misc") {
77 | HStack{
78 | Text("Namespace")
79 | Spacer()
80 | Text(secret.namespace)
81 | }
82 |
83 | }
84 | }
85 | .navigationTitle("Secret")
86 | }
87 | }
88 |
89 | //struct SecretView_Previews: PreviewProvider {
90 | // static var previews: some View {
91 | // SecretView(secret: Secret(id: "abc", name: "abc", labels: ["l1":"l1v"], annotations: ["l1":"l1v"], namespace: "default", data: ["l1":"l1v"]), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
92 | // }
93 | //}
94 |
--------------------------------------------------------------------------------
/K8sCat/inner/ConfigMapView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigMapView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ConfigMapView: View {
11 | let configMap: ConfigMap
12 | let viewModel: ViewModel
13 |
14 | var body: some View {
15 | Form {
16 | Section(header: "Name") {
17 | Text(configMap.name)
18 | }
19 | Section(header: "Data") {
20 |
21 | List {
22 | ForEach((configMap.data ?? [:]).sorted(by: >), id: \.key) {
23 | key, value in
24 | NavigationLink {
25 | Form{
26 | Section(header: "Name"){
27 | Text(key)
28 | }
29 | Section(header: "Data"){
30 | Text(value)
31 | }
32 |
33 |
34 | }
35 | } label: {
36 | VStack(alignment: .leading) {
37 | Text(key)
38 | }
39 | }
40 | }
41 | }
42 |
43 |
44 |
45 | }
46 | Section(header: "Labels and Annotations") {
47 | NavigationLink {
48 | List {
49 | ForEach((configMap.labels ?? [:]).sorted(by: >), id: \.key) {
50 | key, value in
51 | VStack(alignment: .leading) {
52 | Text(key)
53 |
54 | CaptionText(text: value)
55 | }
56 | }
57 | }
58 |
59 | } label: {
60 | Text("Labels")
61 | }
62 | NavigationLink {
63 | List {
64 | ForEach((configMap.annotations ?? [:]).sorted(by: >), id: \.key) {
65 | key, value in
66 | VStack(alignment: .leading) {
67 | Text(key)
68 | CaptionText(text: value)
69 | }
70 | }
71 | }
72 | } label: {
73 | Text("Annotations")
74 | }
75 | }
76 |
77 | Section(header: "Misc") {
78 | HStack{
79 | Text("Namespace")
80 | Spacer()
81 | Text(configMap.namespace)
82 | }
83 |
84 | }
85 | }
86 | .navigationTitle("Config Map")
87 | }
88 | }
89 |
90 | struct ConfigMapView_Previews: PreviewProvider {
91 | static var previews: some View {
92 | ConfigMapView(configMap: ConfigMap(id: "abc", name: "abc", labels: ["l1":"l1v"], annotations: ["l1":"l1v"], namespace: "default", data: ["l1":"l1v"]), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/K8sCat/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "size": "20x20",
5 | "idiom": "iphone",
6 | "filename": "icon-20@2x.png",
7 | "scale": "2x"
8 | },
9 | {
10 | "size": "20x20",
11 | "idiom": "iphone",
12 | "filename": "icon-20@3x.png",
13 | "scale": "3x"
14 | },
15 | {
16 | "size": "29x29",
17 | "idiom": "iphone",
18 | "filename": "icon-29.png",
19 | "scale": "1x"
20 | },
21 | {
22 | "size": "29x29",
23 | "idiom": "iphone",
24 | "filename": "icon-29@2x.png",
25 | "scale": "2x"
26 | },
27 | {
28 | "size": "29x29",
29 | "idiom": "iphone",
30 | "filename": "icon-29@3x.png",
31 | "scale": "3x"
32 | },
33 | {
34 | "size": "40x40",
35 | "idiom": "iphone",
36 | "filename": "icon-40@2x.png",
37 | "scale": "2x"
38 | },
39 | {
40 | "size": "40x40",
41 | "idiom": "iphone",
42 | "filename": "icon-40@3x.png",
43 | "scale": "3x"
44 | },
45 | {
46 | "size": "60x60",
47 | "idiom": "iphone",
48 | "filename": "icon-60@2x.png",
49 | "scale": "2x"
50 | },
51 | {
52 | "size": "60x60",
53 | "idiom": "iphone",
54 | "filename": "icon-60@3x.png",
55 | "scale": "3x"
56 | },
57 | {
58 | "size": "20x20",
59 | "idiom": "ipad",
60 | "filename": "icon-20-ipad.png",
61 | "scale": "1x"
62 | },
63 | {
64 | "size": "20x20",
65 | "idiom": "ipad",
66 | "filename": "icon-20@2x-ipad.png",
67 | "scale": "2x"
68 | },
69 | {
70 | "size": "29x29",
71 | "idiom": "ipad",
72 | "filename": "icon-29-ipad.png",
73 | "scale": "1x"
74 | },
75 | {
76 | "size": "29x29",
77 | "idiom": "ipad",
78 | "filename": "icon-29@2x-ipad.png",
79 | "scale": "2x"
80 | },
81 | {
82 | "size": "40x40",
83 | "idiom": "ipad",
84 | "filename": "icon-40.png",
85 | "scale": "1x"
86 | },
87 | {
88 | "size": "40x40",
89 | "idiom": "ipad",
90 | "filename": "icon-40@2x.png",
91 | "scale": "2x"
92 | },
93 | {
94 | "size": "76x76",
95 | "idiom": "ipad",
96 | "filename": "icon-76.png",
97 | "scale": "1x"
98 | },
99 | {
100 | "size": "76x76",
101 | "idiom": "ipad",
102 | "filename": "icon-76@2x.png",
103 | "scale": "2x"
104 | },
105 | {
106 | "size": "83.5x83.5",
107 | "idiom": "ipad",
108 | "filename": "icon-83.5@2x.png",
109 | "scale": "2x"
110 | },
111 | {
112 | "size": "1024x1024",
113 | "idiom": "ios-marketing",
114 | "filename": "icon-1024.png",
115 | "scale": "1x"
116 | }
117 | ],
118 | "info": {
119 | "version": 1,
120 | "author": "icon.wuruihong.com"
121 | }
122 | }
--------------------------------------------------------------------------------
/K8sCat/inner/HpaView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HpaView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2023/1/2.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct HpaView: View {
11 | let hpa: Hpa
12 | let viewModel: ViewModel
13 |
14 | @State var deployment: Deployment?
15 | @State var showYaml = false
16 | var body: some View {
17 | Form {
18 | Section(header: "Name") {
19 | Text(hpa.name)
20 | }
21 | Section(header: "Reference") {
22 | NavigationLink {
23 | switch hpa.referenceType {
24 | case .Deployment:
25 | DeploymentView(deployment: deployment, viewModel: viewModel)
26 | .onAppear {
27 | Task {
28 | self.deployment = await viewModel.model.deploymentByName(ns: viewModel.ns, name: hpa.reference)
29 | }
30 | }
31 | default:
32 | EmptyView()
33 | }
34 | } label: {
35 |
36 | switch hpa.referenceType {
37 | case .Deployment:
38 | Image(systemName: "ipad.landscape.badge.play")
39 | default:
40 | EmptyView()
41 | }
42 | Text(hpa.reference)
43 | }
44 | }
45 | Section(header: "Misc") {
46 | HStack{
47 | Text("Namespace")
48 | Spacer()
49 | Text(hpa.namespace)
50 | }
51 |
52 | }
53 | }.toolbar{
54 | Menu {
55 | Button {
56 | // do something
57 | // let yaml = hpa.encodeYaml(client: viewModel.model.client)
58 | // print("Yaml: \(yaml)")
59 | showYaml = true
60 | // deployment.decodeYaml(client: viewModel.model.client, yaml: yaml)
61 | } label: {
62 | Text("View/Edit Yaml")
63 | Image(systemName: "note.text")
64 | }
65 | // Button {
66 | // // do something
67 | // } label: {
68 | // Text("Delete Resource")
69 | // Image(systemName: "trash")
70 | // }
71 | } label: {
72 | Image(systemName: "ellipsis")
73 | }
74 | }.sheet(isPresented: $showYaml){
75 | #if os(iOS)
76 | YamlWebView(yamlble: hpa, model: viewModel.model) {
77 | showYaml = false
78 | }
79 | #endif
80 | Button{
81 | Task {
82 | await urlScheme(yamlble: hpa, client: viewModel.model.client)
83 | }
84 | }label: {
85 | Text("Load yaml via Yamler")
86 | }.padding()
87 | }
88 | .navigationTitle("Horizontal Pod Autoscaler")
89 | }
90 | }
91 |
92 | struct HpaView_Previews: PreviewProvider {
93 | static var previews: some View {
94 | HpaView(hpa: Hpa(id: "123", name: "123", namespace: "default",reference: "123", referenceType: .Deployment, raw: nil), viewModel: ViewModel(viewContext: PersistenceController.shared.container.viewContext))
95 | }
96 | }
97 |
98 | enum HPAReference: String {
99 | case Deployment
100 | case UnKnow
101 | }
102 |
--------------------------------------------------------------------------------
/K8sCat/inner/PVView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PVView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/29.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PVView: View {
11 | let pv: PersistentVolume
12 | let viewModel: ViewModel
13 |
14 | @State var showYaml = false
15 | var body: some View {
16 | Form {
17 | Section(header: "Name") {
18 | Text(pv.name)
19 | }
20 | Section(header: "Status") {
21 | Text(pv.status)
22 | }
23 | Section(header: "Access Modes") {
24 | Text(pv.accessModes)
25 | }
26 | Section(header: "Storage Class") {
27 | Text(pv.storageClass)
28 | }
29 | Section(header: "Labels and Annotations") {
30 | NavigationLink {
31 | List {
32 | ForEach((pv.labels ?? [:]).sorted(by: >), id: \.key) {
33 | key, value in
34 | VStack(alignment: .leading) {
35 | Text(key)
36 |
37 | CaptionText(text: value)
38 | }
39 | }
40 | }
41 |
42 | } label: {
43 | Text("Labels")
44 | }
45 | NavigationLink {
46 | List {
47 | ForEach((pv.annotations ?? [:]).sorted(by: >), id: \.key) {
48 | key, value in
49 | VStack(alignment: .leading) {
50 | Text(key)
51 | CaptionText(text: value)
52 | }
53 | }
54 | }
55 | } label: {
56 | Text("Annotations")
57 | }
58 | }
59 |
60 | }.toolbar{
61 | Menu {
62 | Button {
63 | // do something
64 | let yaml = pv.encodeYaml(client: viewModel.model.client)
65 | print("Yaml: \(yaml)")
66 | showYaml = true
67 | // deployment.decodeYaml(client: viewModel.model.client, yaml: yaml)
68 | } label: {
69 | Text("View/Edit Yaml")
70 | Image(systemName: "note.text")
71 | }
72 | // Button {
73 | // // do something
74 | // } label: {
75 | // Text("Delete Resource")
76 | // Image(systemName: "trash")
77 | // }
78 | } label: {
79 | Image(systemName: "ellipsis")
80 | }
81 | }.sheet(isPresented: $showYaml){
82 | #if os(iOS)
83 | YamlWebView(yamlble: pv, model: viewModel.model) {
84 | showYaml = false
85 | }
86 | #endif
87 | Button {
88 | Task {
89 | await urlScheme(yamlble: pv, client: viewModel.model.client)
90 | }
91 | }label: {
92 | Text("Load yaml via Yamler")
93 | }.padding()
94 | }
95 | .navigationTitle("Persistent Volume")
96 | }
97 | }
98 |
99 | //struct PVView_Previews: PreviewProvider {
100 | // static var previews: some View {
101 | // PVView(pv: PersistentVolume(id: "123", name: "123", labels: ["l1":"l1v"], annotations: ["l1":"l1v"],accessModes: "r/w",status: "B",storageClass: "M"), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
102 | // }
103 | //}
104 |
--------------------------------------------------------------------------------
/K8sCat/inner/JobView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JobView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct JobView: View {
11 | @State var pods: [Pod] = []
12 | let job: Job
13 | let viewModel: ViewModel
14 | var body: some View {
15 | Form {
16 | Section(header: "Name") {
17 | Text(job.name)
18 | }
19 | Section(header: "Status") {
20 | Text(job.status ? "Succeeded" : "Failed")
21 | }
22 | Section(header: "Pods") {
23 | List {
24 | ForEach(pods) {
25 | i in
26 | NavigationLink {
27 | PodView(pod: i, viewModel: viewModel)
28 | } label: {
29 | Image(systemName: "tray.2")
30 | VStack(alignment: .leading) {
31 | Text(i.name)
32 | HStack{
33 | Text("containers -").font(.caption)
34 | CaptionText(text: "expect: \(i.expect), ")
35 | Text("error: \(i.status == PodStatus.Succeeded.rawValue ? 0 : i.error)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.error) > 0 ? .red : .none)
36 | Text("not ready: \(i.status == PodStatus.Succeeded.rawValue ? 0 :i.notReady)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.notReady) > 0 ? .yellow : .none)
37 | }
38 |
39 | }
40 | }
41 | }
42 |
43 | }
44 | }
45 | .task {
46 | pods = await viewModel.model.podsByJob(in: .namespace(viewModel.ns), job: job.k8sName)
47 | }
48 | Section(header: "Labels and Annotations") {
49 | NavigationLink {
50 | List {
51 | ForEach((job.labels ?? [:]).sorted(by: >), id: \.key) {
52 | key, value in
53 | VStack(alignment: .leading) {
54 | Text(key)
55 |
56 | CaptionText(text: value)
57 | }
58 | }
59 | }
60 |
61 | } label: {
62 | Text("Labels")
63 | }
64 | NavigationLink {
65 | List {
66 | ForEach((job.annotations ?? [:]).sorted(by: >), id: \.key) {
67 | key, value in
68 | VStack(alignment: .leading) {
69 | Text(key)
70 | CaptionText(text: value)
71 | }
72 | }
73 | }
74 | } label: {
75 | Text("Annotations")
76 | }
77 | }
78 |
79 | Section(header: "Misc") {
80 | HStack{
81 | Text("Namespace")
82 | Spacer()
83 | Text(job.namespace)
84 | }
85 |
86 | }
87 | }
88 | .navigationTitle("Job")
89 | }
90 | }
91 |
92 | struct JobView_Previews: PreviewProvider {
93 | static var previews: some View {
94 | JobView(job: Job(id: "123", name: "123", k8sName: [:], labels: ["l1":"l1v"], annotations: ["l1":"l1v"], namespace: "default", status: false), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/K8sCat/StorageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StorageView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/29.
6 | //
7 |
8 | import SwiftUI
9 | import SwiftUIX
10 |
11 | struct StorageView: View {
12 | @FetchRequest(
13 | sortDescriptors: [],
14 | animation: .default)
15 | private var cluters: FetchedResults
16 | @State var search = ""
17 | @State var tabIndex = 0
18 | @ObservedObject var viewModel: ViewModel
19 | @State var showCluster = false
20 | @Environment(\.managedObjectContext) private var viewContext
21 | var body: some View {
22 | VStack {
23 | NavigationStack {
24 | HStack{
25 | SearchBar(text: $search).padding(.horizontal)
26 | Button{showCluster = true}label: {
27 | Image(systemName: cluters.filter{$0.selected}.first?.icon ?? "0.circle")
28 | }.padding(.trailing)
29 | }
30 | StorageTabBar(tabIndex: $tabIndex).padding(.horizontal, 26)
31 | switch tabIndex {
32 | case 0:
33 | List {
34 | ForEach(viewModel.pv.filter{$0.name.contains(search.lowercased()) || search == ""}) {
35 | i in
36 | NavigationLink {
37 | PVView(pv: i, viewModel: viewModel)
38 | } label: {
39 | Image(systemName: "capsule.portrait")
40 | Text(i.name)
41 | }
42 |
43 | }
44 | }.listStyle(PlainListStyle())
45 | .onAppear {
46 | Task {
47 | await viewModel.pv()
48 | }
49 | }
50 | .refreshable {
51 | viewModel.model.pvs = nil
52 | Task {
53 | await viewModel.pv()
54 | }
55 | }
56 |
57 | case 1:
58 | List {
59 | ForEach(viewModel.pvc.filter{$0.name.contains(search.lowercased()) || search == ""}) {
60 | i in
61 | NavigationLink {
62 | PVCView(pvc: i, viewModel: viewModel)
63 | } label: {
64 | Image(systemName: "shield")
65 | Text(i.name)
66 | }
67 |
68 | }
69 | }.listStyle(PlainListStyle())
70 | .onAppear {
71 | Task {
72 | await viewModel.pvc()
73 | }
74 | }
75 | .refreshable {
76 | viewModel.model.pvcs = nil
77 | Task {
78 | await viewModel.pvc()
79 | }
80 | }
81 |
82 | default:
83 | EmptyView()
84 | }
85 | }
86 | }.sheet(isPresented: $showCluster){
87 | ClusterView(viewModel: viewModel){
88 | showCluster = false
89 | }
90 | .environment(\.managedObjectContext, viewContext)
91 | }
92 | }
93 | }
94 |
95 | struct StorageView_Previews: PreviewProvider {
96 | static var previews: some View {
97 | StorageView(viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/K8sCat/inner/ReplicaView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReplicaView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ReplicaView: View {
11 | let replica: Replica
12 | let viewModel: ViewModel
13 |
14 | @State var pods: [Pod] = []
15 |
16 | var body: some View {
17 | Form {
18 | Section(header: "Name") {
19 | Text(replica.name)
20 | }
21 | Section(header: "Status") {
22 | Text(replica.status ? "Ready" : "Pending")
23 | }
24 | Section(header: "Pods") {
25 | List {
26 | ForEach(pods) {
27 | i in
28 | NavigationLink {
29 | PodView(pod: i, viewModel: viewModel)
30 | } label: {
31 | Image(systemName: "tray.2")
32 | VStack(alignment: .leading) {
33 | Text(i.name)
34 | HStack{
35 | Text("containers -").font(.caption)
36 | CaptionText(text: "expect: \(i.expect), ")
37 | Text("error: \(i.status == PodStatus.Succeeded.rawValue ? 0 : i.error)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.error) > 0 ? .red : .none)
38 | Text("not ready: \(i.status == PodStatus.Succeeded.rawValue ? 0 :i.notReady)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.notReady) > 0 ? .yellow : .none)
39 | }
40 |
41 | }
42 | }
43 | }
44 |
45 | }
46 | }
47 | .task {
48 | pods = await viewModel.model.podsByReplica(in: .namespace(viewModel.ns), replica: replica.k8sName)
49 | }
50 | Section(header: "Labels and Annotations") {
51 | NavigationLink {
52 | List {
53 | ForEach((replica.labels ?? [:]).sorted(by: >), id: \.key) {
54 | key, value in
55 | VStack(alignment: .leading) {
56 | Text(key)
57 |
58 | CaptionText(text: value)
59 | }
60 | }
61 | }
62 |
63 | } label: {
64 | Text("Labels")
65 | }
66 | NavigationLink {
67 | List {
68 | ForEach((replica.annotations ?? [:]).sorted(by: >), id: \.key) {
69 | key, value in
70 | VStack(alignment: .leading) {
71 | Text(key)
72 | CaptionText(text: value)
73 | }
74 | }
75 | }
76 | } label: {
77 | Text("Annotations")
78 | }
79 | }
80 |
81 | Section(header: "Misc") {
82 | HStack{
83 | Text("Namespace")
84 | Spacer()
85 | Text(replica.namespace)
86 | }
87 |
88 | }
89 | }
90 | .navigationTitle("Replica Set")
91 | }
92 | }
93 |
94 | struct ReplicaView_Previews: PreviewProvider {
95 | static var previews: some View {
96 | ReplicaView(replica: Replica(id: "abc", name: "abc", k8sName: [:], labels: ["l1":"l1v"],annotations: ["a1":"a1v"],namespace: "default", status: true), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/K8sCat.xcodeproj/xcshareddata/xcschemes/K8sCat.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
34 |
40 |
41 |
42 |
45 |
51 |
52 |
53 |
54 |
55 |
65 |
67 |
73 |
74 |
75 |
76 |
82 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/K8sCat/yaml/static/js/787.eb3a1d75.chunk.js:
--------------------------------------------------------------------------------
1 | "use strict";(self.webpackChunkedit_yaml=self.webpackChunkedit_yaml||[]).push([[787],{787:function(e,t,n){n.r(t),n.d(t,{getCLS:function(){return y},getFCP:function(){return g},getFID:function(){return C},getLCP:function(){return P},getTTFB:function(){return D}});var i,r,a,o,u=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},f=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},s=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t)}),!0)},m=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},v=-1,d=function(){return"hidden"===document.visibilityState?0:1/0},p=function(){f((function(e){var t=e.timeStamp;v=t}),!0)},l=function(){return v<0&&(v=d(),p(),s((function(){setTimeout((function(){v=d(),p()}),0)}))),{get firstHiddenTime(){return v}}},g=function(e,t){var n,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(f&&f.disconnect(),e.startTime-1&&e(t)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},d=c("layout-shift",v);d&&(n=m(i,r,t),f((function(){d.takeRecords().map(v),n(!0)})),s((function(){a=0,T=-1,r=u("CLS",0),n=m(i,r,t)})))},E={passive:!0,capture:!0},w=new Date,L=function(e,t){i||(i=t,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){L(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",n,E),addEventListener("pointercancel",i,E)}(t,e):L(t,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,b,E)}))},C=function(e,t){var n,a=l(),v=u("FID"),d=function(e){e.startTimeperformance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("load",(function(){return setTimeout(t,0)}))}}}]);
2 | //# sourceMappingURL=787.eb3a1d75.chunk.js.map
--------------------------------------------------------------------------------
/K8sCat/cluster/ConfigView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/31.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ConfigView: View {
11 | @Environment(\.managedObjectContext) private var viewContext
12 | let first: Bool
13 | let close: () -> Void
14 | @State var name = ""
15 | @State var icon = "a.circle"
16 | @State var importMsg: String = "Import Kube Config"
17 | @State var content = ""
18 | @State var showErrorMessage = false
19 | @State var errorMessage = ""
20 |
21 | @State private var showingExporter = false
22 |
23 | var body: some View {
24 | NavigationStack {
25 | Form {
26 |
27 | Section(header: "Name"){
28 | TextField(text: $name)
29 | .disableAutocorrection(true)
30 | .textInputAutocapitalization(.never)
31 | }
32 | Section(header: "Icon"){
33 | Picker("icon", selection: $icon){
34 | ForEach(["a.circle", "b.circle", "c.circle", "e.circle", "f.circle", "g.circle", "h.circle", "i.circle", "j.circle", "k.circle", "l.circle", "m.circle", "n.circle", "o.circle", "p.circle", "q.circle", "r.circle", "s.circle", "t.circle", "u.circle", "v.circle", "w.circle", "x.circle", "y.circle"], id: \.self) {
35 | Image(systemName: $0)
36 | }
37 |
38 | }
39 |
40 | }
41 | Section(header: "Kube Config"){
42 | Text(importMsg).onTapGesture {
43 | showingExporter = true
44 | }
45 | }
46 | Button{
47 | // save to core data
48 | addItem()
49 |
50 | } label: {
51 | Text("Save")
52 | }
53 | }
54 |
55 | }
56 | .alert(errorMessage, isPresented: $showErrorMessage){
57 | Button("OK", role: .cancel) {
58 | showErrorMessage = false
59 | }
60 | }
61 | .fileImporter(isPresented: $showingExporter, allowedContentTypes: [.yaml]) { result in
62 | switch result {
63 | case .success(let url):
64 | let _ = url.startAccessingSecurityScopedResource()
65 | if let content = try? String(contentsOf: url) {
66 | self.content = content
67 | importMsg = "Imported"
68 | print("Import content is \(content)")
69 | }
70 | url.stopAccessingSecurityScopedResource()
71 |
72 | case .failure(let error):
73 | print(error.localizedDescription)
74 | }
75 | }
76 | }
77 |
78 | private func addItem() {
79 | if name.isEmpty {
80 | showErrorMessage = true
81 | errorMessage = "Input cluster name, please."
82 | return
83 | }
84 | if content.isEmpty {
85 | showErrorMessage = true
86 | errorMessage = "Import kube config, please."
87 | return
88 | }
89 | withAnimation {
90 | let newItem = ClusterEntry(context: viewContext)
91 | newItem.name = self.name
92 | newItem.type = ClusterType.KubeConfig.rawValue
93 | newItem.icon = icon
94 | newItem.config = self.content
95 | if first {
96 | newItem.selected = true
97 | }
98 |
99 | do {
100 | try viewContext.save()
101 | } catch {
102 | // Replace this implementation with code to handle the error appropriately.
103 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
104 | let nsError = error as NSError
105 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
106 | }
107 | }
108 | close()
109 | }
110 | }
111 |
112 | struct ConfigView_Previews: PreviewProvider {
113 | static var previews: some View {
114 | ConfigView(first: true){}
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/K8sCat/inner/NodeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NodeView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/27.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct NodeView: View {
11 | let node: Node
12 | let viewModel: ViewModel
13 | var body: some View {
14 | Form {
15 | Section(header: "Name") {
16 | Text(node.name)
17 | }
18 | // Section(header: "Status") {
19 | // Text(node.status)
20 | // }
21 | Section(header: "Role") {
22 | HStack{
23 | Text("Etcd")
24 | Spacer()
25 | Text(String(node.etcd))
26 | }
27 | HStack{
28 | Text("Control Plane")
29 | Spacer()
30 | Text(String(node.controlPlane))
31 | }
32 | HStack{
33 | Text("Worker")
34 | Spacer()
35 | Text(String(node.worker))
36 | }
37 | HStack{
38 | Text("Agent")
39 | Spacer()
40 | Text(String(node.agent))
41 | }
42 | }
43 | // Section(header: "Pods") {
44 | // List {
45 | // ForEach(viewModel.model.podsByDeployment(in: .namespace(viewModel.ns), deployment: deployment.k8sName)) {
46 | // i in
47 | // NavigationLink {
48 | // PodView(pod: i, viewModel: viewModel)
49 | // } label: {
50 | // VStack(alignment: .leading) {
51 | // Text(i.name).foregroundColor(.green)
52 | // HStack{
53 | // CaptionText(text: "expect: \(i.expect), ")
54 | // CaptionText(text: "pendding: \(i.pending)").foregroundColor(i.pending > 0 ? .red : .none)
55 | // }
56 | //
57 | // }
58 | // }
59 | // }
60 | // }
61 | // }
62 | Section(header: "Labels and Annotations") {
63 | NavigationLink {
64 | List {
65 | ForEach((node.labels ?? [:]).sorted(by: >), id: \.key) {
66 | key, value in
67 | VStack(alignment: .leading) {
68 | Text(key)
69 |
70 | CaptionText(text: value)
71 | }
72 | }
73 | }
74 |
75 | } label: {
76 | Text("Labels")
77 | }
78 | NavigationLink {
79 | List {
80 | ForEach((node.annotations ?? [:]).sorted(by: >), id: \.key) {
81 | key, value in
82 | VStack(alignment: .leading) {
83 | Text(key)
84 | CaptionText(text: value)
85 | }
86 | }
87 | }
88 | } label: {
89 | Text("Annotations")
90 | }
91 | }
92 |
93 | Section(header: "Misc") {
94 | HStack{
95 | Text("Host Name")
96 | Spacer()
97 | Text(node.hostName)
98 | }
99 | HStack{
100 | Text("Kube Version")
101 | Spacer()
102 | Text(node.version)
103 | }
104 | HStack{
105 | Text("Arch")
106 | Spacer()
107 | Text(node.arch)
108 | }
109 | HStack{
110 | Text("OS")
111 | Spacer()
112 | Text(node.os)
113 | }
114 | }
115 | }
116 | .navigationTitle("Node")
117 | }
118 | }
119 |
120 | //struct NodeView_Previews: PreviewProvider {
121 | // static var previews: some View {
122 | // NodeView(node: Node(id: "123", name: "123", hostName: "10.0.0.4", arch: "x86", os: "linux", labels: ["l1":"l1v"],annotations: ["a1":"a1v"], etcd: true,worker: true,controlPlane: true, version: "1.1"),viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
123 | // }
124 | //}
125 |
--------------------------------------------------------------------------------
/K8sCat/id/Identifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Identifier.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/21.
6 | //
7 |
8 | import Foundation
9 | import SwiftkubeClient
10 | import NIO
11 | import Yams
12 | import NIOSSL
13 | import AsyncHTTPClient
14 |
15 |
16 | protocol CertIdentifier {
17 | func config() throws -> KubernetesClientConfig?
18 | }
19 |
20 | struct AWS: CertIdentifier {
21 | let awsId: String
22 | let awsSecret: String
23 | let region: String
24 | let clusterName: String
25 |
26 | func config() throws -> KubernetesClientConfig? {
27 | let token = MyAWSClient(ak: awsId, sk: awsSecret, region: region, clusterName: clusterName).getToken()
28 | let c = MyAWSClient(ak: awsId, sk: awsSecret, region: region, clusterName: clusterName).getCluster()
29 | if let c = c,let token = token {
30 | if let data = Data(base64Encoded: c.ca) {
31 | let caCert = try NIOSSLCertificate.fromPEMBytes([UInt8](data))
32 | let authentication = KubernetesClientAuthentication.bearer(token: token)
33 | if let url = URL(string: c.sever) {
34 | let config = KubernetesClientConfig(
35 | masterURL: url,
36 | namespace: "default",
37 | authentication: authentication,
38 | trustRoots: NIOSSLTrustRoots.certificates(caCert),
39 | insecureSkipTLSVerify: false,
40 | timeout: HTTPClient.Configuration.Timeout.init(connect: .seconds(1), read: .seconds(10)),
41 | redirectConfiguration: HTTPClient.Configuration.RedirectConfiguration.follow(max: 5, allowCycles: false)
42 | )
43 |
44 | return config
45 | } else {
46 | return nil
47 | }
48 | } else {
49 | return nil
50 | }
51 | } else {
52 | return nil
53 | }
54 |
55 | }
56 |
57 |
58 | }
59 |
60 | struct Config: CertIdentifier {
61 | let content: String
62 |
63 | func config() throws -> KubernetesClientConfig? {
64 | let decoder = YAMLDecoder()
65 | guard let kubeConfig = try? decoder.decode(KubeConfig.self, from: content) else {
66 | return nil
67 | }
68 | if kubeConfig.clusters == nil || kubeConfig.contexts == nil || kubeConfig.users == nil {
69 | return nil
70 | }
71 | // print("\(kubeConfig)")
72 | let currentContext = kubeConfig.currentContext
73 | let ctxs = kubeConfig.contexts?.filter{$0.name == currentContext}
74 | if ctxs == nil || ctxs!.isEmpty {
75 | return nil
76 | }
77 | let context = ctxs!.first!
78 | let cluters = kubeConfig.clusters?.filter{$0.name == context.context.cluster}
79 | if cluters == nil || cluters!.isEmpty {
80 | return nil
81 | }
82 | let cluster = cluters!.first!
83 | let users = kubeConfig.users?.filter{$0.name == context.context.user}
84 | if users == nil || users!.isEmpty {
85 | return nil
86 | }
87 | let user = users!.first!
88 | if let clientCertificateData = user.authInfo.clientCertificateData,let clientKeyData = user.authInfo.clientKeyData,let certificateAuthorityData = cluster.cluster.certificateAuthorityData {
89 | let p = try NIOSSLCertificate(bytes: .init(clientCertificateData), format: NIOSSLSerializationFormats.pem)
90 | let k = try NIOSSLPrivateKey(bytes: .init(clientKeyData), format: NIOSSLSerializationFormats.pem)
91 |
92 | let authentication = KubernetesClientAuthentication.x509(clientCertificate: p, clientKey: k)
93 |
94 | let caCert = try NIOSSLCertificate.fromPEMBytes([UInt8](certificateAuthorityData))
95 | if let url = URL(string: cluster.cluster.server) {
96 | let config = KubernetesClientConfig(
97 | masterURL: url,
98 | namespace: context.context.namespace ?? "default",
99 | authentication: authentication,
100 | trustRoots: NIOSSLTrustRoots.certificates(caCert),
101 | insecureSkipTLSVerify: false,
102 | timeout: HTTPClient.Configuration.Timeout.init(connect: .seconds(1), read: .seconds(10)),
103 | redirectConfiguration: HTTPClient.Configuration.RedirectConfiguration.follow(max: 5, allowCycles: false)
104 | )
105 | return config
106 | } else {
107 | return nil
108 | }
109 | } else {
110 | return nil
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/K8sCat/inner/CronJobView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CronJobView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CronJobView: View {
11 | let cronJob: CronJob
12 | let viewModel: ViewModel
13 |
14 | @State var showYaml = false
15 | var body: some View {
16 | Form {
17 | Section(header: "Name") {
18 | Text(cronJob.name)
19 | }
20 | Section(header: "Schedule") {
21 | Text(cronJob.schedule)
22 | }
23 | // link job
24 | // Section(header: "Pods") {
25 | // List {
26 | // ForEach(viewModel.model.podsByCronJob(in: .namespace(viewModel.ns), cronJob: cronJob.k8sName)) {
27 | // i in
28 | // NavigationLink {
29 | // PodView(pod: i, viewModel: viewModel)
30 | // } label: {
31 | // Image(systemName: "tray.2")
32 | // VStack(alignment: .leading) {
33 | // Text(i.name).foregroundColor(.green)
34 | // HStack{
35 | // Text("containers -").font(.caption)
36 | // CaptionText(text: "expect: \(i.expect), ")
37 | // CaptionText(text: "warning: \(i.warning)").foregroundColor(i.warning > 0 ? .red : .none)
38 | // }
39 | //
40 | // }
41 | // }
42 | // }
43 | // }
44 | // }
45 | Section(header: "Labels and Annotations") {
46 | NavigationLink {
47 | List {
48 | ForEach((cronJob.labels ?? [:]).sorted(by: >), id: \.key) {
49 | key, value in
50 | VStack(alignment: .leading) {
51 | Text(key)
52 |
53 | CaptionText(text: value)
54 | }
55 | }
56 | }
57 |
58 | } label: {
59 | Text("Labels")
60 | }
61 | NavigationLink {
62 | List {
63 | ForEach((cronJob.annotations ?? [:]).sorted(by: >), id: \.key) {
64 | key, value in
65 | VStack(alignment: .leading) {
66 | Text(key)
67 | CaptionText(text: value)
68 | }
69 | }
70 | }
71 | } label: {
72 | Text("Annotations")
73 | }
74 | }
75 |
76 | Section(header: "Misc") {
77 | HStack{
78 | Text("Namespace")
79 | Spacer()
80 | Text(cronJob.namespace)
81 | }
82 |
83 | }
84 | }.toolbar{
85 | Menu {
86 | Button {
87 | // do something
88 | let yaml = cronJob.encodeYaml(client: viewModel.model.client)
89 | print("Yaml: \(yaml)")
90 | showYaml = true
91 | // deployment.decodeYaml(client: viewModel.model.client, yaml: yaml)
92 | } label: {
93 | Text("View/Edit Yaml")
94 | Image(systemName: "note.text")
95 | }
96 | // Button {
97 | // // do something
98 | // } label: {
99 | // Text("Delete Resource")
100 | // Image(systemName: "trash")
101 | // }
102 | } label: {
103 | Image(systemName: "ellipsis")
104 | }
105 | }.sheet(isPresented: $showYaml){
106 | #if os(iOS)
107 | YamlWebView(yamlble: cronJob, model: viewModel.model) {
108 | showYaml = false
109 | }
110 | #endif
111 | Button{
112 | Task {
113 | await urlScheme(yamlble: cronJob, client: viewModel.model.client)
114 | }
115 | }label: {
116 | Text("Load yaml via Yamler")
117 | }.padding()
118 | }
119 | .navigationTitle("Cron Job")
120 | }
121 | }
122 |
123 | //struct CronJobView_Previews: PreviewProvider {
124 | // static var previews: some View {
125 | // CronJobView(cronJob: CronJob(id: "123", name: "123", k8sName: "123", labels: ["l1":"l1v"], annotations: ["l1":"l1v"], namespace: "default", schedule: "10 / 5 * * *"), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
126 | // }
127 | //}
128 |
--------------------------------------------------------------------------------
/K8sCat/inner/ServiceView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ServiceView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ServiceView: View {
11 | let service: Service
12 | let viewModel: ViewModel
13 |
14 | @State var pods: [Pod] = []
15 | var body: some View {
16 | Form {
17 | Section(header: "Name") {
18 | Text(service.name)
19 | }
20 | Section(header: "Pods") {
21 | List {
22 | ForEach(pods) {
23 | i in
24 | NavigationLink {
25 | PodView(pod: i, viewModel: viewModel)
26 | } label: {
27 | Image(systemName: "tray.2")
28 | VStack(alignment: .leading) {
29 | Text(i.name).foregroundColor(i.status == PodStatus.Failed.rawValue ? .red : (i.status == PodStatus.Running.rawValue || i.status == PodStatus.Succeeded.rawValue ? .green : .yellow))
30 | HStack{
31 | Text("containers -").font(.caption)
32 | CaptionText(text: "expect: \(i.expect), ")
33 | Text("error: \(i.status == PodStatus.Succeeded.rawValue ? 0 : i.error)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.error) > 0 ? .red : .none)
34 | Text("not ready: \(i.status == PodStatus.Succeeded.rawValue ? 0 :i.notReady)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.notReady) > 0 ? .yellow : .none)
35 | }
36 |
37 | }
38 | }
39 | }
40 | }
41 |
42 | }
43 | .task {
44 | pods = await viewModel.model.podsByService(in: .namespace(viewModel.ns), service: service.k8sName)
45 | }
46 | Section(header: "Labels and Annotations") {
47 | NavigationLink {
48 | List {
49 | ForEach((service.labels ?? [:]).sorted(by: >), id: \.key) {
50 | key, value in
51 | VStack(alignment: .leading) {
52 | Text(key)
53 |
54 | CaptionText(text: value)
55 | }
56 | }
57 | }
58 |
59 | } label: {
60 | Text("Labels")
61 | }
62 | NavigationLink {
63 | List {
64 | ForEach((service.annotations ?? [:]).sorted(by: >), id: \.key) {
65 | key, value in
66 | VStack(alignment: .leading) {
67 | Text(key)
68 | CaptionText(text: value)
69 | }
70 | }
71 | }
72 | } label: {
73 | Text("Annotations")
74 | }
75 | }
76 | Section(header: "Ip") {
77 | List {
78 |
79 | VStack(alignment: .leading) {
80 | Text("Cluster IPs")
81 | ForEach(service.clusterIps ?? ["None"], id: \.self) {
82 | ip in
83 |
84 | CaptionText(text: ip)
85 |
86 | }
87 | }
88 |
89 | VStack(alignment: .leading) {
90 | Text("External IPs")
91 | ForEach(service.externalIps ?? ["None"], id: \.self) {
92 | ip in
93 |
94 | CaptionText(text: ip)
95 |
96 | }
97 | }
98 | }
99 | }
100 | Section(header: "Misc") {
101 | HStack{
102 | Text("Namespace")
103 | Spacer()
104 | Text(service.namespace)
105 | }
106 |
107 | }
108 | }
109 | .navigationTitle("Service")
110 | }
111 | }
112 |
113 | struct ServiceView_Previews: PreviewProvider {
114 | static var previews: some View {
115 | ServiceView(service: Service(id: "123", name: "123", k8sName: [:], type: "clusterIP", clusterIps: ["10.0.0.3"], externalIps: nil, labels: ["l1":"l1v"],annotations: ["a1":"a1v"],namespace: "default"), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/K8sCat/inner/DaemonView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DaemonView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct DaemonView: View {
11 | let daemon: Daemon
12 | let viewModel: ViewModel
13 |
14 | @State var pods: [Pod] = []
15 | @State var showYaml = false
16 | var body: some View {
17 | Form {
18 | Section(header: "Name") {
19 | Text(daemon.name)
20 | }
21 | Section(header: "Status") {
22 | Text(daemon.status ? "Ready" : "Schedule")
23 | }
24 | Section(header: "Pods") {
25 | List {
26 | ForEach(pods) {
27 | i in
28 | NavigationLink {
29 | PodView(pod: i, viewModel: viewModel)
30 | } label: {
31 | Image(systemName: "tray.2")
32 | VStack(alignment: .leading) {
33 | Text(i.name)
34 | HStack{
35 | Text("containers -").font(.caption)
36 | CaptionText(text: "expect: \(i.expect), ")
37 | Text("error: \(i.status == PodStatus.Succeeded.rawValue ? 0 : i.error)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.error) > 0 ? .red : .none)
38 | Text("not ready: \(i.status == PodStatus.Succeeded.rawValue ? 0 :i.notReady)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.notReady) > 0 ? .yellow : .none)
39 | }
40 |
41 | }
42 | }
43 | }
44 |
45 | }
46 | }
47 | .task {
48 | pods = await viewModel.model.podsByDaemon(in: .namespace(viewModel.ns), daemon: daemon.k8sName)
49 | }
50 | Section(header: "Labels and Annotations") {
51 | NavigationLink {
52 | List {
53 | ForEach((daemon.labels ?? [:]).sorted(by: >), id: \.key) {
54 | key, value in
55 | VStack(alignment: .leading) {
56 | Text(key)
57 |
58 | CaptionText(text: value)
59 | }
60 | }
61 | }
62 |
63 | } label: {
64 | Text("Labels")
65 | }
66 | NavigationLink {
67 | List {
68 | ForEach((daemon.annotations ?? [:]).sorted(by: >), id: \.key) {
69 | key, value in
70 | VStack(alignment: .leading) {
71 | Text(key)
72 | CaptionText(text: value)
73 | }
74 | }
75 | }
76 | } label: {
77 | Text("Annotations")
78 | }
79 | }
80 |
81 | Section(header: "Misc") {
82 | HStack{
83 | Text("Namespace")
84 | Spacer()
85 | Text(daemon.namespace)
86 | }
87 |
88 | }
89 | }.toolbar{
90 | Menu {
91 | Button {
92 | // do something
93 | // let yaml = daemon.encodeYaml(client: viewModel.model.client)
94 | // print("Yaml: \(yaml)")
95 | showYaml = true
96 | // deployment.decodeYaml(client: viewModel.model.client, yaml: yaml)
97 | } label: {
98 | Text("View/Edit Yaml")
99 | Image(systemName: "note.text")
100 | }
101 | // Button {
102 | // // do something
103 | // } label: {
104 | // Text("Delete Resource")
105 | // Image(systemName: "trash")
106 | // }
107 | } label: {
108 | Image(systemName: "ellipsis")
109 | }
110 | }.sheet(isPresented: $showYaml){
111 | #if os(iOS)
112 | YamlWebView(yamlble: daemon, model: viewModel.model) {
113 | showYaml = false
114 | }
115 | #endif
116 | Button{
117 | Task {
118 | await urlScheme(yamlble: daemon, client: viewModel.model.client)
119 | }
120 | }label: {
121 | Text("Load yaml via Yamler")
122 | }.padding()
123 | }
124 | .navigationTitle("Daemon Set")
125 | }
126 | }
127 |
128 | struct DaemonView_Previews: PreviewProvider {
129 | static var previews: some View {
130 | DaemonView(daemon: Daemon(id: "123", name: "123", k8sName: [:], labels: ["l1":"l1v"],annotations: ["a1":"a1v"],namespace: "default", status: true, raw: nil), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/K8sCat/inner/StatefulView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatefulView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct StatefulView: View {
11 | let stateful: Stateful
12 | let viewModel: ViewModel
13 |
14 | @State var pods: [Pod] = []
15 |
16 | @State var showYaml = false
17 | var body: some View {
18 | Form {
19 | Section(header: "Name") {
20 | Text(stateful.name)
21 | }
22 | Section(header: "Status") {
23 | Text(stateful.status ? "Ready" : "Pending")
24 | }
25 | Section(header: "Pods") {
26 | List {
27 | ForEach(pods) {
28 | i in
29 | NavigationLink {
30 | PodView(pod: i, viewModel: viewModel)
31 | } label: {
32 | Image(systemName: "tray.2")
33 | VStack(alignment: .leading) {
34 | Text(i.name)
35 | HStack{
36 | Text("containers -").font(.caption)
37 | CaptionText(text: "expect: \(i.expect), ")
38 | Text("error: \(i.status == PodStatus.Succeeded.rawValue ? 0 : i.error)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.error) > 0 ? .red : .none)
39 | Text("not ready: \(i.status == PodStatus.Succeeded.rawValue ? 0 :i.notReady)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.notReady) > 0 ? .yellow : .none)
40 | }
41 |
42 | }
43 | }
44 | }
45 | }
46 | }
47 | .task {
48 | pods = await viewModel.model.podsByStateful(in: .namespace(viewModel.ns), stateful: stateful.k8sName)
49 | }
50 | Section(header: "Labels and Annotations") {
51 | NavigationLink {
52 | List {
53 | ForEach((stateful.labels ?? [:]).sorted(by: >), id: \.key) {
54 | key, value in
55 | VStack(alignment: .leading) {
56 | Text(key)
57 |
58 | CaptionText(text: value)
59 | }
60 | }
61 | }
62 |
63 | } label: {
64 | Text("Labels")
65 | }
66 | NavigationLink {
67 | List {
68 | ForEach((stateful.annotations ?? [:]).sorted(by: >), id: \.key) {
69 | key, value in
70 | VStack(alignment: .leading) {
71 | Text(key)
72 | CaptionText(text: value)
73 | }
74 | }
75 | }
76 | } label: {
77 | Text("Annotations")
78 | }
79 | }
80 |
81 | Section(header: "Misc") {
82 | HStack{
83 | Text("Namespace")
84 | Spacer()
85 | Text(stateful.namespace)
86 | }
87 |
88 | }
89 | }.toolbar{
90 | Menu {
91 | Button {
92 | // do something
93 | // let yaml = stateful.encodeYaml(client: viewModel.model.client)
94 | // print("Yaml: \(yaml)")
95 | showYaml = true
96 | // deployment.decodeYaml(client: viewModel.model.client, yaml: yaml)
97 | } label: {
98 | Text("View/Edit Yaml")
99 | Image(systemName: "note.text")
100 | }
101 | // Button {
102 | // // do something
103 | // } label: {
104 | // Text("Delete Resource")
105 | // Image(systemName: "trash")
106 | // }
107 | } label: {
108 | Image(systemName: "ellipsis")
109 | }
110 | }.sheet(isPresented: $showYaml){
111 | #if os(iOS)
112 | YamlWebView(yamlble: stateful, model: viewModel.model) {
113 | showYaml = false
114 | }
115 | #endif
116 | Button{
117 | Task {
118 | await urlScheme(yamlble: stateful, client: viewModel.model.client)
119 | }
120 | }label: {
121 | Text("Load yaml via Yamler")
122 | }.padding()
123 | }
124 | .navigationTitle("Stateful Set")
125 | }
126 | }
127 |
128 | struct StatefulView_Previews: PreviewProvider {
129 | static var previews: some View {
130 | StatefulView(stateful: Stateful(id: "123", name: "123", k8sName: [:], labels: ["l1":"l1v"],annotations: ["a1":"a1v"],namespace: "default", status: true, raw: nil), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/K8sCat/tab/TabBarButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabBarButton.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/20.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TabBarButton: View {
11 | let text: String
12 | @Binding var isSelected: Bool
13 | var body: some View {
14 | Text(text)
15 | .fontWeight(isSelected ? .heavy : .regular)
16 | .font(.custom("Avenir", size: 16))
17 | .padding(.vertical, 10)
18 | .border(width: isSelected ? 2 : 1, edges: [.bottom], color: .systemGray)
19 | }
20 | }
21 |
22 | struct NamespacesTabBar: View {
23 | @Binding var tabIndex: Int
24 | var body: some View {
25 | ScrollView(.horizontal, showsIndicators: false) {
26 | HStack(spacing: 20) {
27 | Group {
28 | TabBarButton(text: "Pods", isSelected: .constant(tabIndex == 0))
29 | .onTapGesture { onButtonTapped(index: 0) }
30 | TabBarButton(text: "Deployments", isSelected: .constant(tabIndex == 1))
31 | .onTapGesture { onButtonTapped(index: 1) }
32 | TabBarButton(text: "Job", isSelected: .constant(tabIndex == 2))
33 | .onTapGesture { onButtonTapped(index: 2) }
34 | TabBarButton(text: "Cron Job", isSelected: .constant(tabIndex == 3))
35 | .onTapGesture { onButtonTapped(index: 3) }
36 | TabBarButton(text: "Statefull Sets", isSelected: .constant(tabIndex == 4))
37 | .onTapGesture { onButtonTapped(index: 4) }
38 | TabBarButton(text: "Service", isSelected: .constant(tabIndex == 5))
39 | .onTapGesture { onButtonTapped(index: 5) }
40 | }
41 | Group {
42 | TabBarButton(text: "Config Map", isSelected: .constant(tabIndex == 6))
43 | .onTapGesture { onButtonTapped(index: 6) }
44 | TabBarButton(text: "Secrets", isSelected: .constant(tabIndex == 7))
45 | .onTapGesture { onButtonTapped(index: 7) }
46 | TabBarButton(text: "Daemon Sets", isSelected: .constant(tabIndex == 8))
47 | .onTapGesture { onButtonTapped(index: 8) }
48 | TabBarButton(text: "Replica Sets", isSelected: .constant(tabIndex == 9))
49 | .onTapGesture { onButtonTapped(index: 9) }
50 | TabBarButton(text: "Horizontal Pod Autoscaler", isSelected: .constant(tabIndex == 10))
51 | .onTapGesture { onButtonTapped(index: 10) }
52 | }
53 |
54 | }
55 | }
56 | .border(width: 1, edges: [.bottom], color: .systemGray)
57 | }
58 |
59 | private func onButtonTapped(index: Int) {
60 | withAnimation { tabIndex = index }
61 | }
62 | }
63 |
64 | struct NodesTabBar: View {
65 | @Binding var tabIndex: Int
66 | var body: some View {
67 | ScrollView(.horizontal, showsIndicators: false) {
68 | HStack(spacing: 20) {
69 | TabBarButton(text: "Nodes", isSelected: .constant(tabIndex == 0))
70 | .onTapGesture { onButtonTapped(index: 0) }
71 | }
72 | }
73 | .border(width: 1, edges: [.bottom], color: .black)
74 | }
75 |
76 | private func onButtonTapped(index: Int) {
77 | withAnimation { tabIndex = index }
78 | }
79 | }
80 |
81 | struct StorageTabBar: View {
82 | @Binding var tabIndex: Int
83 | var body: some View {
84 | ScrollView(.horizontal, showsIndicators: false) {
85 | HStack(spacing: 20) {
86 | TabBarButton(text: "Persistent Volumes", isSelected: .constant(tabIndex == 0))
87 | .onTapGesture { onButtonTapped(index: 0) }
88 | TabBarButton(text: "Persistent Volumes Claim", isSelected: .constant(tabIndex == 1))
89 | .onTapGesture { onButtonTapped(index: 1) }
90 | }
91 | }
92 | .border(width: 1, edges: [.bottom], color: .black)
93 | }
94 |
95 | private func onButtonTapped(index: Int) {
96 | withAnimation { tabIndex = index }
97 | }
98 | }
99 |
100 | struct EdgeBorder: Shape {
101 |
102 | var width: CGFloat
103 | var edges: [Edge]
104 |
105 | func path(in rect: CGRect) -> Path {
106 | var path = Path()
107 | for edge in edges {
108 | var x: CGFloat {
109 | switch edge {
110 | case .top, .bottom, .leading: return rect.minX
111 | case .trailing: return rect.maxX - width
112 | }
113 | }
114 |
115 | var y: CGFloat {
116 | switch edge {
117 | case .top, .leading, .trailing: return rect.minY
118 | case .bottom: return rect.maxY - width
119 | }
120 | }
121 |
122 | var w: CGFloat {
123 | switch edge {
124 | case .top, .bottom: return rect.width
125 | case .leading, .trailing: return self.width
126 | }
127 | }
128 |
129 | var h: CGFloat {
130 | switch edge {
131 | case .top, .bottom: return self.width
132 | case .leading, .trailing: return rect.height
133 | }
134 | }
135 | path.addPath(Path(CGRect(x: x, y: y, width: w, height: h)))
136 | }
137 | return path
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/K8sCat/cluster/ClusterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClusterView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/30.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ClusterView: View {
11 |
12 | let viewModel: ViewModel
13 | let close: () -> Void
14 | @Environment(\.managedObjectContext) private var viewContext
15 | @FetchRequest(
16 | sortDescriptors: [],
17 | animation: .default)
18 | private var cluters: FetchedResults
19 |
20 | @State var showClusterType = false
21 | @State var selectedItem: String?
22 | var body: some View {
23 | HStack {
24 | Spacer()
25 | Button{
26 | showClusterType = true
27 | }label:{
28 | Image(systemName: "plus")
29 | }.padding(.trailing)
30 | #if os(iOS)
31 | EditButton()
32 | #endif
33 | }
34 | .sheet(isPresented: $showClusterType){
35 | ClusterTypeView(first: cluters.isEmpty){
36 | showClusterType = false
37 | }
38 | .environment(\.managedObjectContext, viewContext)
39 | }
40 | .padding()
41 | List(selection: $selectedItem) {
42 | ForEach(cluters.map{Cluster(id: $0.name ?? "", name: $0.name ?? "", icon:
43 | $0.icon ?? "", kubeConfig: $0.config, selected: $0.selected )}) {
44 | i in
45 | HStack {
46 | // Image(systemName: i.selected ? "circle.fill" : "circle")
47 | Image(systemName: i.selected ? i.icon + ".fill" : i.icon)
48 | Text(i.name)
49 | }
50 | }.onDelete{
51 | sets in
52 | deleteItems(offsets: sets)
53 | }.onChange(of: selectedItem ?? ""){
54 | c in
55 | selectItem(id: c)
56 | close()
57 | }
58 | }
59 | .listStyle(PlainListStyle())
60 |
61 |
62 | }
63 |
64 |
65 |
66 | fileprivate func selectNext() {
67 | var allUnSelected = true
68 | for cluster in cluters {
69 | if cluster.selected {
70 | allUnSelected = false
71 | }
72 | }
73 | if !cluters.isEmpty && allUnSelected {
74 | if let id = cluters.first?.name {
75 | selectItem(id: id)
76 | }
77 | }
78 | }
79 |
80 | private func deleteItems(offsets: IndexSet) {
81 | withAnimation {
82 | let delete = offsets.map { cluters[$0] }
83 | delete.forEach{if $0.demo && $0.selected {
84 | viewModel.model.hasAndSelectDemo = false
85 |
86 | }}
87 | delete.forEach{
88 | if $0.selected {
89 | viewModel.model.clearAll()
90 | }
91 | }
92 | delete.forEach(viewContext.delete)
93 |
94 | do {
95 | try viewContext.save()
96 | selectNext()
97 | } catch {
98 | // Replace this implementation with code to handle the error appropriately.
99 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
100 | let nsError = error as NSError
101 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
102 | }
103 | }
104 | }
105 |
106 | private func selectItem(id: String) {
107 | withAnimation {
108 | for cluster in cluters {
109 | if cluster.name == id {
110 | cluster.selected = true
111 | if !cluster.demo {
112 | viewModel.model.hasAndSelectDemo = false
113 | }
114 |
115 | } else {
116 | cluster.selected = false
117 | }
118 | }
119 |
120 | do {
121 | try viewContext.save()
122 | viewModel.select(viewContext: viewContext)
123 | let namespaces = viewModel.namespace
124 | viewModel.ns = namespaces.isEmpty ? "default" : namespaces.first!
125 | } catch {
126 | // Replace this implementation with code to handle the error appropriately.
127 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
128 | let nsError = error as NSError
129 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
130 | }
131 | }
132 | }
133 | }
134 |
135 | struct Cluster: Identifiable, Hashable {
136 | var id: String
137 | let name: String
138 | let icon: String
139 | let kubeConfig: String?
140 | let selected: Bool
141 |
142 | }
143 |
144 | enum ClusterType: String, CaseIterable {
145 | case KubeConfig
146 | case Demo
147 | case Aliyun
148 | case AWS
149 | case GCP
150 | case Azuse
151 | case DO
152 | }
153 |
154 | struct ClusterView_Previews: PreviewProvider {
155 | static var previews: some View {
156 | ClusterView(viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext)){}
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/K8sCat/id/MyAWSClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AWSClient.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2023/1/6.
6 | //
7 |
8 | import Foundation
9 | import SotoEKS
10 | import SotoSTS
11 |
12 | struct MyAWSClient {
13 |
14 |
15 | let ak: String
16 | let sk: String
17 | let region: String
18 | let clusterName: String
19 |
20 |
21 | static let TOKEN_PREFIX = "k8s-aws-v1."
22 | func getCluster() -> AWSCluster? {
23 | let client = AWSClient(
24 | credentialProvider: .static(accessKeyId: ak, secretAccessKey: sk),
25 | httpClientProvider: .createNew
26 | )
27 | var eks: EKS?
28 | switch region {
29 | case "us-west-1": eks = EKS(client: client, region: .uswest1)
30 | case "us-east-1": eks = EKS(client: client, region: .useast1)
31 | case "us-east-2": eks = EKS(client: client, region: .useast2)
32 | case "us-west-2": eks = EKS(client: client, region: .uswest2)
33 | case "ca-central-1": eks = EKS(client: client, region: .cacentral1)
34 | case "eu-west-1": eks = EKS(client: client, region: .euwest1)
35 | case "eu-west-2": eks = EKS(client: client, region: .euwest2)
36 | case "eu-west-3": eks = EKS(client: client, region: .euwest3)
37 | case "eu-south-1": eks = EKS(client: client, region: .eusouth1)
38 | case "eu-south-2": eks = EKS(client: client, region: .eusouth2)
39 | case "eu-central-1": eks = EKS(client: client, region: .eucentral1)
40 | case "eu-central-2": eks = EKS(client: client, region: .eucentral2)
41 | case "ap-northeast-1": eks = EKS(client: client, region: .apnortheast1)
42 | case "ap-northeast-2": eks = EKS(client: client, region: .apnortheast2)
43 | case "ap-northeast-3": eks = EKS(client: client, region: .apnortheast3)
44 | case "ap-southeast-1": eks = EKS(client: client, region: .apsoutheast1)
45 | case "ap-southeast-2": eks = EKS(client: client, region: .apsoutheast2)
46 | case "ap-southeast-3": eks = EKS(client: client, region: .apsoutheast3)
47 | case "ap-south-1": eks = EKS(client: client, region: .apsouth1)
48 | case "ap-south-2": eks = EKS(client: client, region: .apsouth2)
49 | case "sa-east-1": eks = EKS(client: client, region: .saeast1)
50 | default: break
51 | }
52 | let r = EKS.DescribeClusterRequest(name: clusterName)
53 | if let eks = eks {
54 | let c = try? eks.describeCluster(r).wait().cluster
55 | let server = c?.endpoint
56 | let ca = c?.certificateAuthority?.data
57 | try? client.syncShutdown()
58 | if let c = c,let server = server,let ca = ca {
59 | return AWSCluster(sever: server, ca: ca)
60 | } else {
61 | return nil
62 | }
63 |
64 | } else {
65 | return nil
66 | }
67 |
68 | }
69 |
70 | func getToken() -> String? {
71 | let client = AWSClient(
72 | credentialProvider: .static(accessKeyId: ak, secretAccessKey: sk),
73 | httpClientProvider: .createNew
74 | )
75 | var sts: STS?
76 | switch region {
77 | case "us-west-1": sts = STS(client: client, region: .uswest1)
78 | case "us-east-1": sts = STS(client: client, region: .useast1)
79 | case "us-east-2": sts = STS(client: client, region: .useast2)
80 | case "us-west-2": sts = STS(client: client, region: .uswest2)
81 | case "ca-central-1": sts = STS(client: client, region: .cacentral1)
82 | case "eu-west-1": sts = STS(client: client, region: .euwest1)
83 | case "eu-west-2": sts = STS(client: client, region: .euwest2)
84 | case "eu-west-3": sts = STS(client: client, region: .euwest3)
85 | case "eu-south-1": sts = STS(client: client, region: .eusouth1)
86 | case "eu-south-2": sts = STS(client: client, region: .eusouth2)
87 | case "eu-central-1": sts = STS(client: client, region: .eucentral1)
88 | case "eu-central-2": sts = STS(client: client, region: .eucentral2)
89 | case "ap-northeast-1": sts = STS(client: client, region: .apnortheast1)
90 | case "ap-northeast-2": sts = STS(client: client, region: .apnortheast2)
91 | case "ap-northeast-3": sts = STS(client: client, region: .apnortheast3)
92 | case "ap-southeast-1": sts = STS(client: client, region: .apsoutheast1)
93 | case "ap-southeast-2": sts = STS(client: client, region: .apsoutheast2)
94 | case "ap-southeast-3": sts = STS(client: client, region: .apsoutheast3)
95 | case "ap-south-1": sts = STS(client: client, region: .apsouth1)
96 | case "ap-south-2": sts = STS(client: client, region: .apsouth2)
97 | case "sa-east-1": sts = STS(client: client, region: .saeast1)
98 | default: break
99 | }
100 | if let sts = sts,let stsEndpoint = URL(string: sts.endpoint+"/?Action=GetCallerIdentity&Version=2011-06-15") {
101 | let url = try? sts.signURL(
102 | url: stsEndpoint,
103 | httpMethod: .GET,
104 | headers: ["x-k8s-aws-id": clusterName],
105 | expires: .seconds(60)
106 | ).wait()
107 | // print("signed: \(url.absoluteString)")
108 | if let url = url, let tokenSurfix = Base64FS.encodeString(str: url.absoluteString) {
109 | var token = MyAWSClient.TOKEN_PREFIX + tokenSurfix
110 | token.remove(at: token.index(before: token.endIndex))
111 | token.remove(at: token.index(before: token.endIndex))
112 | // print("sts: \(token)")
113 | try? client.syncShutdown()
114 | return token
115 | } else {
116 | return nil
117 | }
118 | } else {
119 | return nil
120 | }
121 | }
122 | }
123 |
124 | struct AWSCluster {
125 | let sever: String
126 | let ca: String
127 | }
128 |
--------------------------------------------------------------------------------
/K8sCat/Ex.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ex.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/20.
6 | //
7 |
8 | import SwiftUI
9 | import SwiftkubeClient
10 | import SwiftkubeModel
11 |
12 | protocol Yamlble {
13 | func encodeYaml(client: KubernetesClient?) async -> String
14 |
15 | func decodeYamlAndUpdate(client: KubernetesClient?, yaml: String) async
16 | }
17 |
18 | extension Bundle {
19 | var releaseVersionNumber: String? {
20 | return infoDictionary?["CFBundleShortVersionString"] as? String
21 | }
22 | var buildVersionNumber: String? {
23 | return infoDictionary?["CFBundleVersion"] as? String
24 | }
25 | }
26 |
27 | extension View {
28 | func border(width: CGFloat, edges: [Edge], color: SwiftUI.Color) -> some View {
29 | overlay(EdgeBorder(width: width, edges: edges).foregroundColor(color))
30 | }
31 | }
32 |
33 | struct Hpa: Identifiable, Yamlble {
34 | var id: String
35 | var name: String
36 | // let k8sName: String
37 | // let labels: [String: String]?
38 | // let annotations: [String: String]?
39 | let namespace: String
40 | let reference: String
41 | let referenceType: HPAReference
42 | let raw: autoscaling.v2.HorizontalPodAutoscaler?
43 | }
44 |
45 | struct Pod: Identifiable {
46 | var id: String
47 | var name: String
48 | let k8sName: String
49 | let status: String
50 | let expect: Int
51 | let error: Int // container other status
52 | let notReady: Int
53 | let containers: [Container]
54 | let clusterIP: String
55 | let nodeIP: String
56 | let labels: [String: String]?
57 | let annotations: [String: String]?
58 | let namespace: String
59 | let controllerType: PodControllerType
60 | let controllerName: String
61 | let raw: core.v1.Pod?
62 | }
63 |
64 | struct Container: Identifiable {
65 | var id: String
66 | var name: String
67 | let image: String
68 |
69 | let path: String
70 | let policy: String
71 |
72 | let pullPolicy: String
73 | let status: ContainerStatus
74 | let ready: Bool
75 | let error: Bool
76 | }
77 |
78 | struct Deployment: Identifiable, Yamlble {
79 | var id: String
80 | var name: String
81 | let k8sName: [String: String]
82 | // let status: String
83 | let expect: Int
84 | let unavailable: Int
85 | let labels: [String: String]?
86 | let annotations: [String: String]?
87 | let namespace: String
88 | let status: Bool
89 | let raw: apps.v1.Deployment?
90 | }
91 |
92 | struct PersistentVolume: Identifiable, Yamlble {
93 | var id: String
94 | var name: String
95 | let labels: [String: String]?
96 | let annotations: [String: String]?
97 |
98 | // let capactiy: String
99 | let accessModes: String
100 | // let reclaim: String
101 | let status: String
102 | let storageClass: String
103 | let raw: core.v1.PersistentVolume?
104 | }
105 |
106 | struct PersistentVolumeClaim: Identifiable {
107 | var id: String
108 | var name: String
109 | // let labels: [String: String]?
110 | // let annotations: [String: String]?
111 | }
112 |
113 | struct Job: Identifiable {
114 | var id: String
115 | var name: String
116 | let k8sName: [String: String]
117 | let labels: [String: String]?
118 | let annotations: [String: String]?
119 | let namespace: String
120 | let status: Bool
121 | }
122 |
123 | struct CronJob: Identifiable, Yamlble {
124 | var id: String
125 | var name: String
126 | let k8sName: [String: String]
127 | let labels: [String: String]?
128 | let annotations: [String: String]?
129 | let namespace: String
130 | let schedule: String
131 | let raw: batch.v1.CronJob?
132 | }
133 |
134 | struct Stateful: Identifiable, Yamlble {
135 | var id: String
136 | var name: String
137 | let k8sName: [String: String]
138 | let labels: [String: String]?
139 | let annotations: [String: String]?
140 | let namespace: String
141 | let status: Bool
142 | let raw: apps.v1.StatefulSet?
143 | }
144 |
145 | struct Service: Identifiable {
146 | var id: String
147 | var name: String
148 | let k8sName: [String: String]
149 | let type: String
150 | let clusterIps: [String]?
151 | let externalIps: [String]?
152 | let labels: [String: String]?
153 | let annotations: [String: String]?
154 | let namespace: String
155 | // let ports: Int
156 | }
157 |
158 | struct ConfigMap: Identifiable {
159 | var id: String
160 | var name: String
161 | let labels: [String: String]?
162 | let annotations: [String: String]?
163 | let namespace: String
164 | let data: [String: String]?
165 | }
166 |
167 | struct Secret: Identifiable {
168 | var id: String
169 | var name: String
170 | let labels: [String: String]?
171 | let annotations: [String: String]?
172 | let namespace: String
173 | let data: [String: String]?
174 | }
175 |
176 | struct Daemon: Identifiable, Yamlble {
177 | var id: String
178 | var name: String
179 | let k8sName: [String: String]
180 | let labels: [String: String]?
181 | let annotations: [String: String]?
182 | let namespace: String
183 | let status: Bool
184 | let raw: apps.v1.DaemonSet?
185 | }
186 |
187 | struct Replica: Identifiable {
188 | var id: String
189 | var name: String
190 | var k8sName: [String: String]
191 | let labels: [String: String]?
192 | let annotations: [String: String]?
193 | let namespace: String
194 | let status: Bool
195 | }
196 |
197 | //struct Replication: Identifiable {
198 | // var id: String
199 | // var name: String
200 | //}
201 | struct Node: Identifiable {
202 | var id: String
203 | var name: String
204 | var hostName: String
205 | var arch: String
206 | var os: String
207 |
208 | let labels: [String: String]?
209 | let annotations: [String: String]?
210 |
211 | let etcd: Bool
212 | let worker: Bool
213 | let controlPlane: Bool
214 | let agent: Bool
215 | var version: String
216 |
217 | // var age: String
218 | }
219 |
--------------------------------------------------------------------------------
/K8sCat/Model+Controller.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Model+Name.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2023/1/11.
6 | //
7 |
8 | import Foundation
9 | import SwiftkubeModel
10 |
11 | extension Model {
12 | mutating func daemonByName(ns: String, name: String) async -> Daemon {
13 | checkAWSToken()
14 | if let client = client {
15 | let daemonOrNil = try? await client.appsV1.daemonSets.get(in: .namespace(ns), name: name)
16 | if let daemon = daemonOrNil {
17 | return Daemon(id: daemon.name ?? "", name: daemon.name ?? "", k8sName: (daemon.spec?.selector.matchLabels) ?? [:]
18 | , labels: daemon.metadata?.labels
19 | , annotations: daemon.metadata?.annotations
20 | , namespace: daemon.metadata?.namespace ?? "unknow"
21 | , status: !(daemon.status?.numberMisscheduled ?? 0 > 0)
22 | , raw: daemon)
23 | } else {
24 | return Daemon(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: true, raw: nil)
25 | }
26 |
27 | } else {
28 | return Daemon(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: true, raw: nil)
29 | }
30 | }
31 |
32 | mutating func replicaByName(ns: String, name: String) async -> Replica {
33 | checkAWSToken()
34 | if let client = client {
35 | let replicaOrNil = try? await client.appsV1.replicaSets.get(in: .namespace(ns), name: name)
36 | if let replica = replicaOrNil {
37 | return Replica(id: replica.name ?? "", name: replica.name ?? "", k8sName: (replica.spec?.selector.matchLabels) ?? [:]
38 | , labels: replica.metadata?.labels
39 | , annotations: replica.metadata?.annotations
40 | , namespace: replica.metadata?.namespace ?? "unknow"
41 | , status: replica.status?.replicas == replica.status?.readyReplicas
42 | )
43 | } else {
44 | return Replica(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: true)
45 | }
46 | } else {
47 | return Replica(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: true)
48 | }
49 | }
50 |
51 | mutating func statefulByName(ns: String, name: String) async -> Stateful {
52 | checkAWSToken()
53 | if let client = client {
54 | let statefulOrNil = try? await client.appsV1.statefulSets.get(in: .namespace(ns), name: name)
55 | if let stateful = statefulOrNil {
56 | return Stateful(id: stateful.name ?? "", name: stateful.name ?? "", k8sName: (stateful.spec?.selector.matchLabels) ?? [:]
57 | , labels: stateful.metadata?.labels
58 | , annotations: stateful.metadata?.annotations
59 | , namespace: stateful.metadata?.namespace ?? "unknow"
60 | , status: stateful.status?.readyReplicas == stateful.status?.replicas, raw: stateful
61 | )
62 | } else {
63 | return Stateful(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: false, raw: nil)
64 | }
65 | } else {
66 | return Stateful(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: false, raw: nil)
67 | }
68 | }
69 |
70 | mutating func jobByName(ns: String, name: String) async -> Job {
71 | checkAWSToken()
72 | if let client = client {
73 | let jobOrNil = try? await client.batchV1.jobs.get(in: .namespace(ns), name: name)
74 | if let job = jobOrNil {
75 | return Job(id: job.name ?? "", name: job.name ?? "",
76 | k8sName: (job.spec?.selector?.matchLabels) ?? [:]
77 | , labels: job.metadata?.labels
78 | , annotations: job.metadata?.annotations
79 | , namespace: job.metadata?.namespace ?? "unknow"
80 | , status: job.status?.succeeded != nil
81 | )
82 | } else {
83 | return Job(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: true)
84 | }
85 | } else {
86 | return Job(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: true)
87 | }
88 | }
89 |
90 | mutating func deploymentByName(ns: String, name: String) async -> Deployment {
91 | checkAWSToken()
92 | if let client = client {
93 | let deploymentOrNil = try? await client.appsV1.deployments.get(in: .namespace(ns), name: name)
94 | if let deployment = deploymentOrNil {
95 | return Deployment(id: deployment.name ?? "", name: deployment.name ?? "", k8sName: (deployment.spec?.selector.matchLabels) ?? [:], expect: Int(deployment.spec?.replicas ?? 0), unavailable: Int(deployment.status?.unavailableReplicas ?? 0)
96 | , labels: deployment.metadata?.labels
97 | , annotations: deployment.metadata?.annotations
98 | , namespace: deployment.metadata?.namespace ?? "unknow"
99 | , status: deployment.status?.replicas == deployment.status?.readyReplicas
100 | , raw: deployment
101 | )
102 | } else {
103 | return Deployment(id: "demo1", name: "demo1", k8sName: [:], expect: 2, unavailable: 0, labels: [:], annotations: [:], namespace: "demo1", status: true, raw: nil)
104 | }
105 | } else {
106 | return Deployment(id: "demo1", name: "demo1", k8sName: [:], expect: 2, unavailable: 0, labels: [:], annotations: [:], namespace: "demo1", status: true, raw: nil)
107 | }
108 | }
109 | }
110 |
111 |
112 |
--------------------------------------------------------------------------------
/K8sCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "async-http-client",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/swift-server/async-http-client.git",
7 | "state" : {
8 | "revision" : "291438696abdd48d2a83b52465c176efbd94512b",
9 | "version" : "1.20.1"
10 | }
11 | },
12 | {
13 | "identity" : "client",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/swiftkube/client",
16 | "state" : {
17 | "revision" : "2787e28749b8e2a2c67f898984c2dbfe8553390f",
18 | "version" : "0.17.0"
19 | }
20 | },
21 | {
22 | "identity" : "jmespath.swift",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/adam-fowler/jmespath.swift.git",
25 | "state" : {
26 | "revision" : "4513d319c4aaa6c3b2ac18e1e6566a803515ad91",
27 | "version" : "1.0.2"
28 | }
29 | },
30 | {
31 | "identity" : "model",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/swiftkube/model.git",
34 | "state" : {
35 | "revision" : "990eed2d25770ad8bc14ef2a74ffc5dc179a41f7",
36 | "version" : "0.13.0"
37 | }
38 | },
39 | {
40 | "identity" : "soto",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/soto-project/soto",
43 | "state" : {
44 | "revision" : "258094b83797fb9ad4fe3882b1a6a46a92d2c2ae",
45 | "version" : "6.4.0"
46 | }
47 | },
48 | {
49 | "identity" : "soto-core",
50 | "kind" : "remoteSourceControl",
51 | "location" : "https://github.com/soto-project/soto-core.git",
52 | "state" : {
53 | "revision" : "02d3054ed054a587ce1778bd946a783abd8f62e3",
54 | "version" : "6.4.0"
55 | }
56 | },
57 | {
58 | "identity" : "swift-algorithms",
59 | "kind" : "remoteSourceControl",
60 | "location" : "https://github.com/apple/swift-algorithms",
61 | "state" : {
62 | "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42",
63 | "version" : "1.2.0"
64 | }
65 | },
66 | {
67 | "identity" : "swift-atomics",
68 | "kind" : "remoteSourceControl",
69 | "location" : "https://github.com/apple/swift-atomics.git",
70 | "state" : {
71 | "revision" : "cd142fd2f64be2100422d658e7411e39489da985",
72 | "version" : "1.2.0"
73 | }
74 | },
75 | {
76 | "identity" : "swift-collections",
77 | "kind" : "remoteSourceControl",
78 | "location" : "https://github.com/apple/swift-collections.git",
79 | "state" : {
80 | "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2",
81 | "version" : "1.0.4"
82 | }
83 | },
84 | {
85 | "identity" : "swift-log",
86 | "kind" : "remoteSourceControl",
87 | "location" : "https://github.com/apple/swift-log.git",
88 | "state" : {
89 | "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
90 | "version" : "1.5.4"
91 | }
92 | },
93 | {
94 | "identity" : "swift-metrics",
95 | "kind" : "remoteSourceControl",
96 | "location" : "https://github.com/apple/swift-metrics.git",
97 | "state" : {
98 | "revision" : "971ba26378ab69c43737ee7ba967a896cb74c0d1",
99 | "version" : "2.4.1"
100 | }
101 | },
102 | {
103 | "identity" : "swift-nio",
104 | "kind" : "remoteSourceControl",
105 | "location" : "https://github.com/apple/swift-nio",
106 | "state" : {
107 | "revision" : "635b2589494c97e48c62514bc8b37ced762e0a62",
108 | "version" : "2.63.0"
109 | }
110 | },
111 | {
112 | "identity" : "swift-nio-extras",
113 | "kind" : "remoteSourceControl",
114 | "location" : "https://github.com/apple/swift-nio-extras.git",
115 | "state" : {
116 | "revision" : "91dd2d61fb772e1311bb5f13b59266b579d77e42",
117 | "version" : "1.15.0"
118 | }
119 | },
120 | {
121 | "identity" : "swift-nio-http2",
122 | "kind" : "remoteSourceControl",
123 | "location" : "https://github.com/apple/swift-nio-http2.git",
124 | "state" : {
125 | "revision" : "d6656967f33ed8b368b38e4b198631fc7c484a40",
126 | "version" : "1.23.1"
127 | }
128 | },
129 | {
130 | "identity" : "swift-nio-ssl",
131 | "kind" : "remoteSourceControl",
132 | "location" : "https://github.com/apple/swift-nio-ssl.git",
133 | "state" : {
134 | "revision" : "4fb7ead803e38949eb1d6fabb849206a72c580f3",
135 | "version" : "2.23.0"
136 | }
137 | },
138 | {
139 | "identity" : "swift-nio-transport-services",
140 | "kind" : "remoteSourceControl",
141 | "location" : "https://github.com/apple/swift-nio-transport-services.git",
142 | "state" : {
143 | "revision" : "6cbe0ed2b394f21ab0d46b9f0c50c6be964968ce",
144 | "version" : "1.20.1"
145 | }
146 | },
147 | {
148 | "identity" : "swift-numerics",
149 | "kind" : "remoteSourceControl",
150 | "location" : "https://github.com/apple/swift-numerics.git",
151 | "state" : {
152 | "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b",
153 | "version" : "1.0.2"
154 | }
155 | },
156 | {
157 | "identity" : "swift-system",
158 | "kind" : "remoteSourceControl",
159 | "location" : "https://github.com/apple/swift-system.git",
160 | "state" : {
161 | "revision" : "025bcb1165deab2e20d4eaba79967ce73013f496",
162 | "version" : "1.2.1"
163 | }
164 | },
165 | {
166 | "identity" : "swiftuix",
167 | "kind" : "remoteSourceControl",
168 | "location" : "https://github.com/SwiftUIX/SwiftUIX",
169 | "state" : {
170 | "revision" : "1c2ba19b91a1541b803f9d1b01b360cae5a752fa",
171 | "version" : "0.1.3"
172 | }
173 | },
174 | {
175 | "identity" : "yams",
176 | "kind" : "remoteSourceControl",
177 | "location" : "https://github.com/jpsim/Yams.git",
178 | "state" : {
179 | "revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3",
180 | "version" : "5.0.6"
181 | }
182 | }
183 | ],
184 | "version" : 2
185 | }
186 |
--------------------------------------------------------------------------------
/K8sCat/Model+Yaml.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Model+Yaml.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2023/1/2.
6 | //
7 |
8 | import Foundation
9 | import Yams
10 | import SwiftkubeModel
11 | import SwiftkubeClient
12 | import SwiftUI
13 |
14 | extension Deployment {
15 | func encodeYaml(client: KubernetesClient?) -> String {
16 | if let _ = client {
17 | let encoder = YAMLEncoder()
18 |
19 | if raw != nil {
20 | let r = try? encoder.encode(raw!)
21 | return r ?? ""
22 | } else {
23 | return ""
24 | }
25 |
26 | } else {
27 | return ""
28 | }
29 | }
30 |
31 | func decodeYamlAndUpdate(client: KubernetesClient?, yaml: String) async {
32 | if let client = client {
33 | let decoder = YAMLDecoder()
34 | let d = try? decoder.decode(apps.v1.Deployment.self, from: yaml)
35 | if d != nil {
36 | let _ = try? await client.appsV1.deployments.update(d!)
37 | }
38 | }
39 | }
40 | }
41 |
42 | extension Stateful {
43 | func encodeYaml(client: KubernetesClient?) -> String {
44 | if let _ = client {
45 | let encoder = YAMLEncoder()
46 |
47 | if raw != nil {
48 | let r = try? encoder.encode(raw!)
49 | return r ?? ""
50 | } else {
51 | return ""
52 | }
53 | } else {
54 | return ""
55 | }
56 | }
57 |
58 | func decodeYamlAndUpdate(client: KubernetesClient?, yaml: String) async {
59 | if let client = client {
60 | let decoder = YAMLDecoder()
61 | let d = try? decoder.decode(apps.v1.StatefulSet.self, from: yaml)
62 | if d != nil {
63 | let _ = try? await client.appsV1.statefulSets.update(d!)
64 | }
65 | }
66 | }
67 | }
68 |
69 | extension Daemon {
70 | func encodeYaml(client: KubernetesClient?) -> String {
71 | if let _ = client {
72 | let encoder = YAMLEncoder()
73 |
74 | if raw != nil {
75 | let r = try? encoder.encode(raw!)
76 | return r ?? ""
77 | } else {
78 | return ""
79 | }
80 | } else {
81 | return ""
82 | }
83 | }
84 |
85 | func decodeYamlAndUpdate(client: KubernetesClient?, yaml: String) async {
86 | if let client = client {
87 | let decoder = YAMLDecoder()
88 | let d = try? decoder.decode(apps.v1.DaemonSet.self, from: yaml)
89 | if d != nil {
90 | let _ = try? await client.appsV1.daemonSets.update(d!)
91 | }
92 | }
93 | }
94 | }
95 |
96 | extension Hpa {
97 | func encodeYaml(client: KubernetesClient?) -> String {
98 | if let _ = client {
99 | let encoder = YAMLEncoder()
100 |
101 | if raw != nil {
102 | let r = try? encoder.encode(raw!)
103 | return r ?? ""
104 | } else {
105 | return ""
106 | }
107 | } else {
108 | return ""
109 | }
110 | }
111 |
112 | func decodeYamlAndUpdate(client: KubernetesClient?, yaml: String) async {
113 | if let client = client {
114 | let decoder = YAMLDecoder()
115 | let d = try? decoder.decode(autoscaling.v2.HorizontalPodAutoscaler.self, from: yaml)
116 | if d != nil {
117 | let _ = try? await client.autoScalingV2.horizontalPodAutoscalers.update(d!)
118 | }
119 | }
120 | }
121 | }
122 |
123 | extension CronJob {
124 | func encodeYaml(client: KubernetesClient?) -> String {
125 | if let _ = client {
126 | let encoder = YAMLEncoder()
127 |
128 | if raw != nil {
129 | let r = try? encoder.encode(raw!)
130 | return r ?? ""
131 | } else {
132 | return ""
133 | }
134 | } else {
135 | return ""
136 | }
137 | }
138 |
139 | func decodeYamlAndUpdate(client: KubernetesClient?, yaml: String) async {
140 | if let client = client {
141 | let decoder = YAMLDecoder()
142 | let d = try? decoder.decode(batch.v1.CronJob.self, from: yaml)
143 | if d != nil {
144 | let _ = try? await client.batchV1.cronJobs.update(d!)
145 | }
146 | }
147 | }
148 | }
149 |
150 | extension PersistentVolume {
151 | func encodeYaml(client: KubernetesClient?) -> String {
152 | if let _ = client {
153 | let encoder = YAMLEncoder()
154 |
155 | if raw != nil {
156 | let r = try? encoder.encode(raw!)
157 | return r ?? ""
158 | } else {
159 | return ""
160 | }
161 | } else {
162 | return ""
163 | }
164 | }
165 |
166 | func decodeYamlAndUpdate(client: KubernetesClient?, yaml: String) async {
167 | if let client = client {
168 | let decoder = YAMLDecoder()
169 | let d = try? decoder.decode(core.v1.PersistentVolume.self, from: yaml)
170 | if d != nil {
171 | let _ = try? await client.persistentVolumes.update(d!)
172 | }
173 | }
174 | }
175 | }
176 |
177 |
178 |
179 | func urlScheme(yamlble: Yamlble, client: KubernetesClient?) async {
180 | let utf8str = await yamlble.encodeYaml(client: client).data(using: .utf8)
181 | if let utf8str = utf8str {
182 | let base64Encoded = utf8str.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0))
183 | #if os(iOS)
184 | if let url = URL(string: "yamler://" + base64Encoded) {
185 | if await UIApplication.shared.canOpenURL(url) {
186 | await UIApplication.shared.open(url)
187 | } else {
188 | if let url = URL(string: "https://apps.apple.com/cn/app/yamler/id1660009640") {
189 | await UIApplication.shared.open(url)
190 | }
191 | }
192 |
193 | }
194 | #endif
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/K8sCat/inner/PodView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PodView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PodView: View {
11 | @State var daemon: Daemon = Daemon(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: true, raw: nil)
12 | @State var replica: Replica = Replica(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: true)
13 | @State var stateful = Stateful(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: false, raw: nil)
14 | @State var job = Job(id: "demo", name: "demo", k8sName: [:], labels: [:], annotations: [:], namespace: "demo", status: true)
15 |
16 | let pod: Pod
17 | let viewModel: ViewModel
18 | var body: some View {
19 | Form {
20 | Section(header: "Name") {
21 | Text(pod.name)
22 | }
23 | Section(header: "Status") {
24 | Text(pod.status)
25 | }
26 | Section(header: "Containers") {
27 | List {
28 | ForEach(pod.containers) {
29 | c in
30 | NavigationLink {
31 | ContainerView(pod: pod, container: c, viewModel: viewModel)
32 | } label: {
33 | Image(systemName: "tray.full")
34 | VStack(alignment: .leading) {
35 | Text(c.name)
36 | .foregroundColor(c.ready ? .green : (c.error ? .red : .yellow))
37 | CaptionText(text: c.image)
38 | }
39 | }
40 |
41 |
42 | }
43 | }
44 | }
45 | Section(header: "Contoller") {
46 | NavigationLink {
47 | switch pod.controllerType {
48 | case .DaemonSet:
49 | DaemonView(daemon: daemon, viewModel: viewModel)
50 | .task {
51 | daemon = await viewModel.model.daemonByName(ns: viewModel.ns, name: pod.controllerName)
52 | }
53 | case .ReplicaSet:
54 | ReplicaView(replica: replica, viewModel: viewModel)
55 | .task {
56 | replica = await viewModel.model.replicaByName(ns: viewModel.ns, name: pod.controllerName)
57 | }
58 | case .StatefulSet:
59 | StatefulView(stateful: stateful, viewModel: viewModel)
60 | .task {
61 | stateful = await viewModel.model.statefulByName(ns: viewModel.ns, name: pod.controllerName)
62 | }
63 | case .Job:
64 | JobView(job: job, viewModel: viewModel)
65 | .task {
66 | job = await viewModel.model.jobByName(ns: viewModel.ns, name: pod.controllerName)
67 | }
68 | default: EmptyView()
69 | }
70 | } label: {
71 | switch pod.controllerType {
72 | case .DaemonSet:
73 | Image(systemName: "xserve")
74 | case .ReplicaSet:
75 | Image(systemName: "square.3.layers.3d.down.left")
76 | case .StatefulSet:
77 | Image(systemName: "macpro.gen2.fill")
78 | case .Job:
79 | Image(systemName: "figure.run")
80 | default: EmptyView()
81 | }
82 | Text(pod.controllerName)
83 | }
84 | }
85 | Section(header: "Labels and Annotations") {
86 | NavigationLink {
87 | List {
88 | ForEach((pod.labels ?? [:]).sorted(by: >), id: \.key) {
89 | key, value in
90 | VStack(alignment: .leading) {
91 | Text(key)
92 |
93 | CaptionText(text: value)
94 | }
95 | }
96 | }
97 |
98 | } label: {
99 | Text("Labels")
100 | }
101 | NavigationLink {
102 | List {
103 | ForEach((pod.annotations ?? [:]).sorted(by: >), id: \.key) {
104 | key, value in
105 | VStack(alignment: .leading) {
106 | Text(key)
107 | CaptionText(text: value)
108 | }
109 | }
110 | }
111 | } label: {
112 | Text("Annotations")
113 | }
114 | }
115 | Section(header: "Ip") {
116 | HStack{
117 | Text("Pod IP")
118 | Spacer()
119 | Text(pod.clusterIP)
120 | }
121 | HStack{
122 | Text("Node IP")
123 | Spacer()
124 | Text(pod.nodeIP)
125 | }
126 | }
127 | Section(header: "Misc") {
128 | HStack{
129 | Text("Namespace")
130 | Spacer()
131 | Text(pod.namespace)
132 | }
133 |
134 | }
135 | }
136 | .navigationTitle("Pod")
137 | .onAppear {
138 | viewModel.model.checkAWSToken()
139 | }
140 | }
141 | }
142 |
143 | //struct PodView_Previews: PreviewProvider {
144 | // static var previews: some View {
145 | // PodView(pod: Pod(id: "123", name: "123", k8sName: "123", status: "fail", expect: 8, warning: 7, containers: [Container(id: "abc", name: "abclong....", image: "hello",path: "/",policy: "r",pullPolicy: "r"), Container(id: "ef", name: "ef", image: "kkk", path: "/", policy: "r", pullPolicy: "r")],clusterIP: "10.0.0.3", nodeIP: "192.168.1.3", labels: ["l1":"l1v"],annotations: ["a1":"a1v"],namespace: "default", raw: nil), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
146 | // }
147 | //}
148 |
149 | enum PodStatus: String {
150 | case Pending
151 | case Running
152 | case Succeeded
153 | case Failed
154 | case Unknown
155 | }
156 |
157 | enum PodControllerType: String {
158 | case ReplicaSet
159 | case Job
160 | case StatefulSet
161 | case DaemonSet
162 | case UnKonw
163 | }
164 |
--------------------------------------------------------------------------------
/K8sCat/inner/DeploymentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeploymentView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/26.
6 | //
7 |
8 | import SwiftUI
9 | // deployment has child
10 | struct DeploymentView: View {
11 | let deployment: Deployment?
12 | let viewModel: ViewModel
13 |
14 | @State var pods: [Pod] = []
15 | @State var replicas: [Replica] = []
16 |
17 | @State var showYaml = false
18 | var body: some View {
19 | if let deployment = deployment {
20 | Form {
21 | Section(header: "Name") {
22 | Text(deployment.name)
23 | }
24 | Section(header: "Status") {
25 | Text(deployment.status ? "Ready" : "Failed")
26 | }
27 | // Button{
28 | // viewModel.model.scaleDeployment(deployment: deployment, replicas: 1)
29 | // } label: {
30 | // Text("scale")
31 | // }
32 | Section(header: "Replica Sets") {
33 | List {
34 | ForEach(replicas) {
35 | i in
36 | NavigationLink {
37 | ReplicaView(replica: i, viewModel: viewModel)
38 | } label: {
39 | Image(systemName: "square.3.layers.3d.down.left")
40 | Text(i.name)
41 | .foregroundColor(i.status ? .green : .red)
42 | }
43 | }
44 | }
45 | }
46 | .task {
47 | replicas = await viewModel.model.replicaByDeployment(in: .namespace(viewModel.ns), deployment: deployment.k8sName)
48 | }
49 | Section(header: "Pods") {
50 | List {
51 | ForEach(pods) {
52 | i in
53 | NavigationLink {
54 | PodView(pod: i, viewModel: viewModel)
55 | } label: {
56 | Image(systemName: "tray.2")
57 | VStack(alignment: .leading) {
58 | Text(i.name)
59 | HStack{
60 | Text("containers -").font(.caption)
61 | CaptionText(text: "expect: \(i.expect), ")
62 | Text("error: \(i.status == PodStatus.Succeeded.rawValue ? 0 : i.error)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.error) > 0 ? .red : .none)
63 | Text("not ready: \(i.status == PodStatus.Succeeded.rawValue ? 0 :i.notReady)").font(.caption).foregroundColor((i.status == PodStatus.Succeeded.rawValue ? 0 : i.notReady) > 0 ? .yellow : .none)
64 | }
65 |
66 | }
67 | }
68 | }
69 |
70 | }
71 | }
72 | .task {
73 | pods = await viewModel.model.podsByDeployment(in: .namespace(viewModel.ns), deployment: deployment.k8sName)
74 | }
75 | Section(header: "Labels and Annotations") {
76 | NavigationLink {
77 | List {
78 | ForEach((deployment.labels ?? [:]).sorted(by: >), id: \.key) {
79 | key, value in
80 | VStack(alignment: .leading) {
81 | Text(key)
82 |
83 | CaptionText(text: value)
84 | }
85 | }
86 | }
87 |
88 | } label: {
89 | Text("Labels")
90 | }
91 | NavigationLink {
92 | List {
93 | ForEach((deployment.annotations ?? [:]).sorted(by: >), id: \.key) {
94 | key, value in
95 | VStack(alignment: .leading) {
96 | Text(key)
97 | CaptionText(text: value)
98 | }
99 | }
100 | }
101 | } label: {
102 | Text("Annotations")
103 | }
104 | }
105 |
106 | Section(header: "Misc") {
107 | HStack{
108 | Text("Namespace")
109 | Spacer()
110 | Text(deployment.namespace)
111 | }
112 |
113 | }
114 | }.toolbar{
115 | Menu {
116 | Button {
117 | // do something
118 | // let yaml = deployment.encodeYaml(client: viewModel.model.client)
119 | // print("Yaml: \(yaml)")
120 | showYaml = true
121 | // deployment.decodeYaml(client: viewModel.model.client, yaml: yaml)
122 | } label: {
123 | Text("View/Edit Yaml")
124 | Image(systemName: "note.text")
125 | }
126 | // Button {
127 | // // do something
128 | // } label: {
129 | // Text("Delete Resource")
130 | // Image(systemName: "trash")
131 | // }
132 | } label: {
133 | Image(systemName: "ellipsis")
134 | }
135 | }.sheet(isPresented: $showYaml){
136 | #if os(iOS)
137 | YamlWebView(yamlble: deployment, model: viewModel.model) {
138 | showYaml = false
139 | }
140 | #endif
141 | Button{
142 | Task {
143 | await urlScheme(yamlble: deployment, client: viewModel.model.client)
144 | }
145 | }label: {
146 | Text("Load yaml via Yamler")
147 | }.padding()
148 | }
149 | .navigationTitle("Deployment")
150 | } else {
151 | EmptyView()
152 | }
153 | }
154 | }
155 |
156 | struct DeploymentView_Previews: PreviewProvider {
157 | static var previews: some View {
158 | DeploymentView(deployment: Deployment(id: "123", name: "123", k8sName: [:], expect: 1, unavailable: 0, labels: ["l1":"l1v"],annotations: ["a1":"a1v"],namespace: "default", status: false, raw: nil), viewModel: ViewModel(viewContext: PersistenceController.preview.container.viewContext))
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/K8sCat/id/Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Token.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2023/1/7.
6 | //
7 |
8 | import Foundation
9 |
10 | public class Base64FS {
11 |
12 | private static let padding: UInt8 = 61 // Padding = "="
13 |
14 | private static let filenameSafeAlphabet: [UInt8] = [
15 | 65, // 0 = "A"
16 | 66, // 1 = "B"
17 | 67, // 2 = "C"
18 | 68, // 3 = "D"
19 | 69, // 4 = "E"
20 | 70, // 5 = "F"
21 | 71, // 6 = "G"
22 | 72, // 7 = "H"
23 | 73, // 8 = "I"
24 | 74, // 9 = "J"
25 | 75, // 10 = "K"
26 | 76, // 11 = "L"
27 | 77, // 12 = "M"
28 | 78, // 13 = "N"
29 | 79, // 14 = "O"
30 | 80, // 15 = "P"
31 | 81, // 16 = "Q"
32 | 82, // 17 = "R"
33 | 83, // 18 = "S"
34 | 84, // 19 = "T"
35 | 85, // 20 = "U"
36 | 86, // 21 = "V"
37 | 87, // 22 = "W"
38 | 88, // 23 = "X"
39 | 89, // 24 = "Y"
40 | 90, // 25 = "Z"
41 | 97, // 26 = "a"
42 | 98, // 27 = "b"
43 | 99, // 28 = "c"
44 | 100, // 29 = "d"
45 | 101, // 30 = "e"
46 | 102, // 31 = "f"
47 | 103, // 32 = "g"
48 | 104, // 33 = "h"
49 | 105, // 34 = "i"
50 | 106, // 35 = "j"
51 | 107, // 36 = "k"
52 | 108, // 37 = "l"
53 | 109, // 38 = "m"
54 | 110, // 39 = "n"
55 | 111, // 40 = "o"
56 | 112, // 41 = "p"
57 | 113, // 42 = "q"
58 | 114, // 43 = "r"
59 | 115, // 44 = "s"
60 | 116, // 45 = "t"
61 | 117, // 46 = "u"
62 | 118, // 47 = "v"
63 | 119, // 48 = "w"
64 | 120, // 49 = "x"
65 | 121, // 50 = "y"
66 | 122, // 51 = "z"
67 | 48, // 52 = "0"
68 | 49, // 53 = "1"
69 | 50, // 54 = "2"
70 | 51, // 55 = "3"
71 | 52, // 56 = "4"
72 | 53, // 57 = "5"
73 | 54, // 58 = "6"
74 | 55, // 59 = "7"
75 | 56, // 60 = "8"
76 | 57, // 61 = "9"
77 | 45, // 62 = "-"
78 | 95, // 63 = "_"
79 | ]
80 |
81 | private static let safeAlphabetToIndex: [UInt8 : UInt8] = [
82 | 61 : 0, // Padding = 0
83 | 65 : 0, // 0 = "A"
84 | 66 : 1, // 1 = "B"
85 | 67 : 2, // 2 = "C"
86 | 68 : 3, // 3 = "D"
87 | 69 : 4, // 4 = "E"
88 | 70 : 5, // 5 = "F"
89 | 71 : 6, // 6 = "G"
90 | 72 : 7, // 7 = "H"
91 | 73 : 8, // 8 = "I"
92 | 74 : 9, // 9 = "J"
93 | 75 : 10, // 10 = "K"
94 | 76 : 11, // 11 = "L"
95 | 77 : 12, // 12 = "M"
96 | 78 : 13, // 13 = "N"
97 | 79 : 14, // 14 = "O"
98 | 80 : 15, // 15 = "P"
99 | 81 : 16, // 16 = "Q"
100 | 82 : 17, // 17 = "R"
101 | 83 : 18, // 18 = "S"
102 | 84 : 19, // 19 = "T"
103 | 85 : 20, // 20 = "U"
104 | 86 : 21, // 21 = "V"
105 | 87 : 22, // 22 = "W"
106 | 88 : 23, // 23 = "X"
107 | 89 : 24, // 24 = "Y"
108 | 90 : 25, // 25 = "Z"
109 | 97 : 26, // 26 = "a"
110 | 98 : 27, // 27 = "b"
111 | 99 : 28, // 28 = "c"
112 | 100 : 29, // 29 = "d"
113 | 101 : 30, // 30 = "e"
114 | 102 : 31, // 31 = "f"
115 | 103 : 32, // 32 = "g"
116 | 104 : 33, // 33 = "h"
117 | 105 : 34, // 34 = "i"
118 | 106 : 35, // 35 = "j"
119 | 107 : 36, // 36 = "k"
120 | 108 : 37, // 37 = "l"
121 | 109 : 38, // 38 = "m"
122 | 110 : 39, // 39 = "n"
123 | 111 : 40, // 40 = "o"
124 | 112 : 41, // 41 = "p"
125 | 113 : 42, // 42 = "q"
126 | 114 : 43, // 43 = "r"
127 | 115 : 44, // 44 = "s"
128 | 116 : 45, // 45 = "t"
129 | 117 : 46, // 46 = "u"
130 | 118 : 47, // 47 = "v"
131 | 119 : 48, // 48 = "w"
132 | 120 : 49, // 49 = "x"
133 | 121 : 50, // 50 = "y"
134 | 122 : 51, // 51 = "z"
135 | 48 : 52, // 52 = "0"
136 | 49 : 53, // 53 = "1"
137 | 50 : 54, // 54 = "2"
138 | 51 : 55, // 55 = "3"
139 | 52 : 56, // 56 = "4"
140 | 53 : 57, // 57 = "5"
141 | 54 : 58, // 58 = "6"
142 | 55 : 59, // 59 = "7"
143 | 56 : 60, // 60 = "8"
144 | 57 : 61, // 61 = "9"
145 | 45 : 62, // 62 = "-"
146 | 95 : 63, // 63 = "_"
147 | ]
148 |
149 |
150 | public static func encodeString(str: String) -> String? {
151 |
152 | // Get the ascii representation and return
153 | let data = str.data(using: .ascii)
154 | if let data = data {
155 | let encData = encode(data: [UInt8](data))
156 |
157 | return String(data: Data(encData), encoding: .ascii)
158 | } else {
159 | return nil
160 | }
161 | }
162 |
163 | public static func encode(data: [UInt8]) -> [UInt8] {
164 |
165 | var result: [UInt8] = []
166 |
167 | let size = data.count
168 |
169 | // Step through 3 bytes at a time
170 | for i in stride(from: 0, to: size, by: 3) {
171 |
172 |
173 | // Get the first 6 bits, and add the Base64 letter
174 | let first = data[i] >> 2
175 | result.append(filenameSafeAlphabet[Int(first)])
176 |
177 | // Get the remaining 2 bits from the previous byte
178 | var second = (data[i] & 0b11) << 4
179 |
180 |
181 | // If there is more of the array, add the next 4 bits from byte 2, or return with padding for the 3rd and 4th characters
182 | if i + 1 < size {
183 | second |= (data[i + 1] & 0b11110000) >> 4
184 | result.append(filenameSafeAlphabet[Int(second)])
185 | } else {
186 | result.append(filenameSafeAlphabet[Int(second)])
187 | result.append(padding)
188 | result.append(padding)
189 | return result
190 | }
191 |
192 |
193 | // Get the remaining 4 bits from the previous byte
194 | var third = (data[i + 1] & 0b1111) << 2
195 |
196 | // If there is more of the array, add the next 2 bits from byte 3, or return with padding for the 4th character
197 | if i + 2 < size {
198 | third |= (data[i + 2] & 0b11000000) >> 6
199 | result.append(filenameSafeAlphabet[Int(third)])
200 | } else {
201 | result.append(filenameSafeAlphabet[Int(third)])
202 | result.append(padding)
203 | return result
204 | }
205 |
206 |
207 | // Get the remaining 6 bits from the previous byte, add to the result
208 | let forth = data[i + 2] & 0b00111111
209 | result.append(filenameSafeAlphabet[Int(forth)])
210 | }
211 |
212 | return result
213 | }
214 |
215 | public static func decode(data: [UInt8]) -> [UInt8] {
216 |
217 | var result: [UInt8] = []
218 |
219 | let size = data.count
220 | // We loop over the 4 letters at a time
221 | // We know it is padded, so we dont need to check for size
222 | for i in stride(from: 0, to: size, by: 4) {
223 |
224 | // Get the 4 letters, then get back to their non-index values
225 | let first = safeAlphabetToIndex[data[i]]!
226 | let second = safeAlphabetToIndex[data[i + 1]]!
227 | let third = safeAlphabetToIndex[data[i + 2]]!
228 | let forth = safeAlphabetToIndex[data[i + 3]]!
229 |
230 | // Get the 3 binary letters from the four 6-bit ones
231 | let l1 = first << 2 | ((second & 0b110000) >> 4)
232 | let l2 = ((second & 0b1111) << 4) | ((third & 0b111100) >> 2)
233 | let l3 = ((third & 0b11) << 6) | forth
234 |
235 | // Return the letters if they arent empty
236 | result.append(l1)
237 |
238 | if l3 != 0 {
239 | result.append(l2)
240 | result.append(l3)
241 | } else if l2 != 0 {
242 | result.append(l2)
243 | }
244 | }
245 |
246 | return result
247 | }
248 |
249 | public static func decodeString(str: String) -> String {
250 |
251 | // Get the ascii representation and return
252 | let data = str.data(using: .ascii)!
253 |
254 | let decData = decode(data: [UInt8](data))
255 |
256 | let retStr = String(data: Data(decData), encoding: .ascii)!
257 |
258 | return retStr
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/K8sCat/cluster/AWSView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigView.swift
3 | // K8sCat
4 | //
5 | // Created by 顾艳华 on 2022/12/31.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AWSView: View {
11 | @Environment(\.managedObjectContext) private var viewContext
12 | let first: Bool
13 | let close: () -> Void
14 | @State var name = ""
15 | @State var icon = "a.circle"
16 | @State var accessKeyID = ""
17 | @State var secretAccessKey = ""
18 | @State var region = "us-west-1"
19 | @State var clusterName = ""
20 |
21 | @State var showHelp = false
22 | @State var showErrorMessage = false
23 | @State var errorMessage = ""
24 |
25 | @State private var showingExporter = false
26 |
27 | var columns = Array(repeating: GridItem(.flexible(), spacing: 10), count: 5)
28 | var body: some View {
29 | NavigationStack {
30 | Form {
31 |
32 | Section(header: "Name"){
33 | TextField(text: $name)
34 | .disableAutocorrection(true)
35 | .textInputAutocapitalization(.never)
36 | }
37 | Section(header: "Icon"){
38 | Picker("Icon", selection: $icon){
39 | ForEach(["a.circle", "b.circle", "c.circle", "e.circle", "f.circle", "g.circle", "h.circle", "i.circle", "j.circle", "k.circle", "l.circle", "m.circle", "n.circle", "o.circle", "p.circle", "q.circle", "r.circle", "s.circle", "t.circle", "u.circle", "v.circle", "w.circle", "x.circle", "y.circle"], id: \.self) {
40 | Image(systemName: $0)
41 | }
42 |
43 | }
44 |
45 | }
46 | Section(header: "Access Key ID"){
47 | TextField(text: $accessKeyID)
48 | .disableAutocorrection(true)
49 | .textInputAutocapitalization(.never)
50 | }
51 | Section(header: "Secret Access key"){
52 | TextField(text: $secretAccessKey)
53 | .disableAutocorrection(true)
54 | .textInputAutocapitalization(.never)
55 | }
56 |
57 | Section(header: "Region"){
58 | Picker("Region", selection: $region){
59 | ForEach([AWSResign(id: "us-east-1", render: "US East (N. Virginia)", value: "us-east-1")
60 | ,AWSResign(id: "us-east-2", render: "US East (Ohio)", value: "us-east-2")
61 | ,AWSResign(id: "us-west-1", render: "US West (N. California)", value: "us-west-1")
62 | ,AWSResign(id: "us-west-2", render: "US West (Oregon)", value: "us-west-2")
63 | ,AWSResign(id: "ca-central-1", render: "Canada (Central)", value: "ca-central-1")
64 | ,AWSResign(id: "eu-west-1", render: "EU (Ireland)", value: "eu-west-1")
65 | ,AWSResign(id: "eu-central-1", render: "EU (Frankfurt)", value: "eu-central-1")
66 | ,AWSResign(id: "eu-central-2", render: "EU (Zurich)", value: "eu-central-2")
67 | ,AWSResign(id: "eu-west-2", render: "EU (Frankfurt)", value: "eu-west-2")
68 | ,AWSResign(id: "eu-west-3", render: "EU (Paris)", value: "eu-west-3")
69 | ,AWSResign(id: "eu-north-1", render: "EU (Stockholm)", value: "eu-north-1")
70 | ,AWSResign(id: "eu-south-1", render: "EU (Milan)", value: "eu-south-1")
71 | ,AWSResign(id: "eu-south-2", render: "EU (Spain)", value: "eu-south-2")
72 | ,AWSResign(id: "ap-northeast-1", render: "Asia Pacific (Tokyo)", value: "ap-northeast-1")
73 | ,AWSResign(id: "ap-northeast-2", render: "Asia Pacific (Seoul)", value: "ap-northeast-2")
74 | ,AWSResign(id: "ap-northeast-3", render: "Asia Pacific (Osaka-Local)", value: "ap-northeast-3")
75 | ,AWSResign(id: "ap-southeast-1", render: "Asia Pacific (Singapore)", value: "ap-southeast-1")
76 | ,AWSResign(id: "ap-southeast-2", render: "Asia Pacific (Sydney)", value: "ap-southeast-2")
77 | ,AWSResign(id: "ap-southeast-3", render: "Asia Pacific (Jakarta)", value: "ap-southeast-3")
78 | ,AWSResign(id: "ap-south-1", render: "Asia Pacific (Mumbai)", value: "ap-south-1")
79 | ,AWSResign(id: "ap-south-2", render: "Asia Pacific (Hyderabad)", value: "ap-south-2")
80 | ,AWSResign(id: "sa-east-1", render: "South America (São Paulo)", value: "sa-east-1")
81 | ]) {
82 | Text($0.render)
83 | }
84 |
85 | }
86 | }
87 | Section(header: "Cluster Name"){
88 | TextField(text: $clusterName)
89 | .disableAutocorrection(true)
90 | .textInputAutocapitalization(.never)
91 | }
92 | Button{
93 | // save to core data
94 | addItem()
95 |
96 | } label: {
97 | Text("Save")
98 | }
99 | }
100 |
101 | }
102 | .alert(errorMessage, isPresented: $showErrorMessage){
103 | Button("OK", role: .cancel) {
104 | showErrorMessage = false
105 | }
106 | }
107 | .toolbar{
108 | Button {
109 | showHelp = true
110 | }label: {
111 | Image(systemName: "questionmark.circle")
112 | }
113 |
114 | }
115 | .sheet(isPresented: $showHelp){
116 | AWSHelpView()
117 | }
118 |
119 | }
120 |
121 | private func addItem() {
122 | if name.isEmpty {
123 | showErrorMessage = true
124 | errorMessage = "Input cluster name, please."
125 | return
126 | }
127 | if accessKeyID.isEmpty {
128 | showErrorMessage = true
129 | errorMessage = "Input AWS access key ID, please."
130 | return
131 | }
132 | if secretAccessKey.isEmpty {
133 | showErrorMessage = true
134 | errorMessage = "Input AWS secret access key, please."
135 | return
136 | }
137 | if region.isEmpty {
138 | showErrorMessage = true
139 | errorMessage = "Input AWS region, please."
140 | return
141 | }
142 | if clusterName.isEmpty {
143 | showErrorMessage = true
144 | errorMessage = "Input AWS EKS cluster name, please."
145 | return
146 | }
147 | // if content.isEmpty {
148 | // showErrorMessage = true
149 | // errorMessage = "Import kube config, please."
150 | // return
151 | // }
152 | withAnimation {
153 | let newItem = ClusterEntry(context: viewContext)
154 | newItem.name = self.name
155 | newItem.type = ClusterType.AWS.rawValue
156 | newItem.icon = icon
157 | newItem.accessKeyID = accessKeyID
158 | newItem.secretAccessKey = secretAccessKey
159 | newItem.region = region
160 | newItem.clusterName = clusterName
161 |
162 | if first {
163 | newItem.selected = true
164 | }
165 |
166 | do {
167 | try viewContext.save()
168 | } catch {
169 | // Replace this implementation with code to handle the error appropriately.
170 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
171 | let nsError = error as NSError
172 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
173 | }
174 | }
175 | close()
176 | }
177 | }
178 |
179 | //struct ConfigView_Previews: PreviewProvider {
180 | // static var previews: some View {
181 | // ConfigView(first: true){}
182 | // }
183 | //}
184 |
185 | struct AWSHelpView: View {
186 | var body: some View {
187 | VStack(alignment: .leading){
188 | Text("Overview").font(.title)
189 | .padding(.vertical)
190 | Text("""
191 | AWS provides a managed Kubernetes service called Amazon EKS (Amazon Elastic Container Service for Kubernetes) making it easy to deploy, manage, and scale containerized applications using Kubernetes on AWS. It's pretty straightforward to import them to Kuber if you're running Kubernetes clusters with EKS.
192 |
193 | It is recommended to use eksctl to build a cluster, refer to: https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html.
194 |
195 | Use the Access key ID and Secret access key of the account used by eksctl.
196 |
197 | Install eksctl: https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html
198 | """)
199 | Text("Select a region").font(.title)
200 | .padding(.vertical)
201 | Text("""
202 | Each EKS powered Kubernetes belongs to a region, so you need to select a region first. Notice that AWS China regions are not supported at this time
203 | """)
204 | Spacer()
205 | }
206 | .padding()
207 |
208 |
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/K8sCat/yaml/static/js/787.eb3a1d75.chunk.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"static/js/787.eb3a1d75.chunk.js","mappings":"uQAAA,IAAIA,EAAEC,EAAEC,EAAEC,EAAEC,EAAE,SAASJ,EAAEC,GAAG,MAAM,CAACI,KAAKL,EAAEM,WAAM,IAASL,GAAG,EAAEA,EAAEM,MAAM,EAAEC,QAAQ,GAAGC,GAAG,MAAMC,OAAOC,KAAKC,MAAM,KAAKF,OAAOG,KAAKC,MAAM,cAAcD,KAAKE,UAAU,MAAM,EAAEC,EAAE,SAAShB,EAAEC,GAAG,IAAI,GAAGgB,oBAAoBC,oBAAoBC,SAASnB,GAAG,CAAC,GAAG,gBAAgBA,KAAK,2BAA2BoB,MAAM,OAAO,IAAIlB,EAAE,IAAIe,qBAAqB,SAASjB,GAAG,OAAOA,EAAEqB,aAAaC,IAAIrB,EAAE,IAAI,OAAOC,EAAEqB,QAAQ,CAACC,KAAKxB,EAAEyB,UAAS,IAAKvB,CAAC,CAAW,CAAT,MAAMF,GAAG,CAAC,EAAE0B,EAAE,SAAS1B,EAAEC,GAAG,IAAIC,EAAE,SAASA,EAAEC,GAAG,aAAaA,EAAEqB,MAAM,WAAWG,SAASC,kBAAkB5B,EAAEG,GAAGF,IAAI4B,oBAAoB,mBAAmB3B,GAAE,GAAI2B,oBAAoB,WAAW3B,GAAE,IAAK,EAAE4B,iBAAiB,mBAAmB5B,GAAE,GAAI4B,iBAAiB,WAAW5B,GAAE,EAAG,EAAE6B,EAAE,SAAS/B,GAAG8B,iBAAiB,YAAY,SAAS7B,GAAGA,EAAE+B,WAAWhC,EAAEC,EAAE,IAAG,EAAG,EAAEgC,EAAE,SAASjC,EAAEC,EAAEC,GAAG,IAAIC,EAAE,OAAO,SAASC,GAAGH,EAAEK,OAAO,IAAIF,GAAGF,KAAKD,EAAEM,MAAMN,EAAEK,OAAOH,GAAG,IAAIF,EAAEM,YAAO,IAASJ,KAAKA,EAAEF,EAAEK,MAAMN,EAAEC,IAAI,CAAC,EAAEiC,GAAG,EAAEC,EAAE,WAAW,MAAM,WAAWR,SAASC,gBAAgB,EAAE,GAAG,EAAEQ,EAAE,WAAWV,GAAG,SAAS1B,GAAG,IAAIC,EAAED,EAAEqC,UAAUH,EAAEjC,CAAC,IAAG,EAAG,EAAEqC,EAAE,WAAW,OAAOJ,EAAE,IAAIA,EAAEC,IAAIC,IAAIL,GAAG,WAAWQ,YAAY,WAAWL,EAAEC,IAAIC,GAAG,GAAG,EAAE,KAAK,CAAKI,sBAAkB,OAAON,CAAC,EAAE,EAAEO,EAAE,SAASzC,EAAEC,GAAG,IAAIC,EAAEC,EAAEmC,IAAIZ,EAAEtB,EAAE,OAAO8B,EAAE,SAASlC,GAAG,2BAA2BA,EAAEK,OAAO+B,GAAGA,EAAEM,aAAa1C,EAAE2C,UAAUxC,EAAEqC,kBAAkBd,EAAEpB,MAAMN,EAAE2C,UAAUjB,EAAElB,QAAQoC,KAAK5C,GAAGE,GAAE,IAAK,EAAEiC,EAAEU,OAAOC,aAAaA,YAAYC,kBAAkBD,YAAYC,iBAAiB,0BAA0B,GAAGX,EAAED,EAAE,KAAKnB,EAAE,QAAQkB,IAAIC,GAAGC,KAAKlC,EAAE+B,EAAEjC,EAAE0B,EAAEzB,GAAGkC,GAAGD,EAAEC,GAAGJ,GAAG,SAAS5B,GAAGuB,EAAEtB,EAAE,OAAOF,EAAE+B,EAAEjC,EAAE0B,EAAEzB,GAAG+C,uBAAuB,WAAWA,uBAAuB,WAAWtB,EAAEpB,MAAMwC,YAAYlC,MAAMT,EAAEkC,UAAUnC,GAAE,EAAG,GAAG,GAAG,IAAI,EAAE+C,GAAE,EAAGC,GAAG,EAAEC,EAAE,SAASnD,EAAEC,GAAGgD,IAAIR,GAAG,SAASzC,GAAGkD,EAAElD,EAAEM,KAAK,IAAI2C,GAAE,GAAI,IAAI/C,EAAEC,EAAE,SAASF,GAAGiD,GAAG,GAAGlD,EAAEC,EAAE,EAAEiC,EAAE9B,EAAE,MAAM,GAAG+B,EAAE,EAAEC,EAAE,GAAGE,EAAE,SAAStC,GAAG,IAAIA,EAAEoD,eAAe,CAAC,IAAInD,EAAEmC,EAAE,GAAGjC,EAAEiC,EAAEA,EAAEiB,OAAO,GAAGlB,GAAGnC,EAAE2C,UAAUxC,EAAEwC,UAAU,KAAK3C,EAAE2C,UAAU1C,EAAE0C,UAAU,KAAKR,GAAGnC,EAAEM,MAAM8B,EAAEQ,KAAK5C,KAAKmC,EAAEnC,EAAEM,MAAM8B,EAAE,CAACpC,IAAImC,EAAED,EAAE5B,QAAQ4B,EAAE5B,MAAM6B,EAAED,EAAE1B,QAAQ4B,EAAElC,IAAI,CAAC,EAAEiD,EAAEnC,EAAE,eAAesB,GAAGa,IAAIjD,EAAE+B,EAAE9B,EAAE+B,EAAEjC,GAAGyB,GAAG,WAAWyB,EAAEG,cAAchC,IAAIgB,GAAGpC,GAAE,EAAG,IAAI6B,GAAG,WAAWI,EAAE,EAAEe,GAAG,EAAEhB,EAAE9B,EAAE,MAAM,GAAGF,EAAE+B,EAAE9B,EAAE+B,EAAEjC,EAAE,IAAI,EAAEsD,EAAE,CAACC,SAAQ,EAAGC,SAAQ,GAAIC,EAAE,IAAI/C,KAAKgD,EAAE,SAASxD,EAAEC,GAAGJ,IAAIA,EAAEI,EAAEH,EAAEE,EAAED,EAAE,IAAIS,KAAKiD,EAAE/B,qBAAqBgC,IAAI,EAAEA,EAAE,WAAW,GAAG5D,GAAG,GAAGA,EAAEC,EAAEwD,EAAE,CAAC,IAAItD,EAAE,CAAC0D,UAAU,cAAczD,KAAKL,EAAEwB,KAAKuC,OAAO/D,EAAE+D,OAAOC,WAAWhE,EAAEgE,WAAWrB,UAAU3C,EAAEqC,UAAU4B,gBAAgBjE,EAAEqC,UAAUpC,GAAGE,EAAE+D,SAAS,SAASlE,GAAGA,EAAEI,EAAE,IAAID,EAAE,EAAE,CAAC,EAAEgE,EAAE,SAASnE,GAAG,GAAGA,EAAEgE,WAAW,CAAC,IAAI/D,GAAGD,EAAEqC,UAAU,KAAK,IAAI1B,KAAKmC,YAAYlC,OAAOZ,EAAEqC,UAAU,eAAerC,EAAEwB,KAAK,SAASxB,EAAEC,GAAG,IAAIC,EAAE,WAAWyD,EAAE3D,EAAEC,GAAGG,GAAG,EAAED,EAAE,WAAWC,GAAG,EAAEA,EAAE,WAAWyB,oBAAoB,YAAY3B,EAAEqD,GAAG1B,oBAAoB,gBAAgB1B,EAAEoD,EAAE,EAAEzB,iBAAiB,YAAY5B,EAAEqD,GAAGzB,iBAAiB,gBAAgB3B,EAAEoD,EAAE,CAAhO,CAAkOtD,EAAED,GAAG2D,EAAE1D,EAAED,EAAE,CAAC,EAAE4D,EAAE,SAAS5D,GAAG,CAAC,YAAY,UAAU,aAAa,eAAekE,SAAS,SAASjE,GAAG,OAAOD,EAAEC,EAAEkE,EAAEZ,EAAE,GAAG,EAAEa,EAAE,SAASlE,EAAEgC,GAAG,IAAIC,EAAEC,EAAEE,IAAIG,EAAErC,EAAE,OAAO6C,EAAE,SAASjD,GAAGA,EAAE2C,UAAUP,EAAEI,kBAAkBC,EAAEnC,MAAMN,EAAEiE,gBAAgBjE,EAAE2C,UAAUF,EAAEjC,QAAQoC,KAAK5C,GAAGmC,GAAE,GAAI,EAAEe,EAAElC,EAAE,cAAciC,GAAGd,EAAEF,EAAE/B,EAAEuC,EAAEP,GAAGgB,GAAGxB,GAAG,WAAWwB,EAAEI,cAAchC,IAAI2B,GAAGC,EAAER,YAAY,IAAG,GAAIQ,GAAGnB,GAAG,WAAW,IAAIf,EAAEyB,EAAErC,EAAE,OAAO+B,EAAEF,EAAE/B,EAAEuC,EAAEP,GAAG/B,EAAE,GAAGF,GAAG,EAAED,EAAE,KAAK4D,EAAE9B,kBAAkBd,EAAEiC,EAAE9C,EAAEyC,KAAK5B,GAAG6C,GAAG,GAAG,EAAEQ,EAAE,CAAC,EAAEC,EAAE,SAAStE,EAAEC,GAAG,IAAIC,EAAEC,EAAEmC,IAAIJ,EAAE9B,EAAE,OAAO+B,EAAE,SAASnC,GAAG,IAAIC,EAAED,EAAE2C,UAAU1C,EAAEE,EAAEqC,kBAAkBN,EAAE5B,MAAML,EAAEiC,EAAE1B,QAAQoC,KAAK5C,GAAGE,IAAI,EAAEkC,EAAEpB,EAAE,2BAA2BmB,GAAG,GAAGC,EAAE,CAAClC,EAAE+B,EAAEjC,EAAEkC,EAAEjC,GAAG,IAAIwC,EAAE,WAAW4B,EAAEnC,EAAEzB,MAAM2B,EAAEkB,cAAchC,IAAIa,GAAGC,EAAEM,aAAa2B,EAAEnC,EAAEzB,KAAI,EAAGP,GAAE,GAAI,EAAE,CAAC,UAAU,SAASgE,SAAS,SAASlE,GAAG8B,iBAAiB9B,EAAEyC,EAAE,CAAC8B,MAAK,EAAGd,SAAQ,GAAI,IAAI/B,EAAEe,GAAE,GAAIV,GAAG,SAAS5B,GAAG+B,EAAE9B,EAAE,OAAOF,EAAE+B,EAAEjC,EAAEkC,EAAEjC,GAAG+C,uBAAuB,WAAWA,uBAAuB,WAAWd,EAAE5B,MAAMwC,YAAYlC,MAAMT,EAAEkC,UAAUgC,EAAEnC,EAAEzB,KAAI,EAAGP,GAAE,EAAG,GAAG,GAAG,GAAG,CAAC,EAAEsE,EAAE,SAASxE,GAAG,IAAIC,EAAEC,EAAEE,EAAE,QAAQH,EAAE,WAAW,IAAI,IAAIA,EAAE6C,YAAY2B,iBAAiB,cAAc,IAAI,WAAW,IAAIzE,EAAE8C,YAAY4B,OAAOzE,EAAE,CAAC6D,UAAU,aAAanB,UAAU,GAAG,IAAI,IAAIzC,KAAKF,EAAE,oBAAoBE,GAAG,WAAWA,IAAID,EAAEC,GAAGW,KAAK8D,IAAI3E,EAAEE,GAAGF,EAAE4E,gBAAgB,IAAI,OAAO3E,CAAC,CAAjL,GAAqL,GAAGC,EAAEI,MAAMJ,EAAEK,MAAMN,EAAE4E,cAAc3E,EAAEI,MAAM,GAAGJ,EAAEI,MAAMwC,YAAYlC,MAAM,OAAOV,EAAEM,QAAQ,CAACP,GAAGD,EAAEE,EAAY,CAAT,MAAMF,GAAG,CAAC,EAAE,aAAa2B,SAASmD,WAAWvC,WAAWtC,EAAE,GAAG6B,iBAAiB,QAAQ,WAAW,OAAOS,WAAWtC,EAAE,EAAE,GAAG,C","sources":["../node_modules/web-vitals/dist/web-vitals.js"],"sourcesContent":["var e,t,n,i,r=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:\"v2-\".concat(Date.now(),\"-\").concat(Math.floor(8999999999999*Math.random())+1e12)}},a=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if(\"first-input\"===e&&!(\"PerformanceEventTiming\"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},o=function(e,t){var n=function n(i){\"pagehide\"!==i.type&&\"hidden\"!==document.visibilityState||(e(i),t&&(removeEventListener(\"visibilitychange\",n,!0),removeEventListener(\"pagehide\",n,!0)))};addEventListener(\"visibilitychange\",n,!0),addEventListener(\"pagehide\",n,!0)},u=function(e){addEventListener(\"pageshow\",(function(t){t.persisted&&e(t)}),!0)},c=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},f=-1,s=function(){return\"hidden\"===document.visibilityState?0:1/0},m=function(){o((function(e){var t=e.timeStamp;f=t}),!0)},v=function(){return f<0&&(f=s(),m(),u((function(){setTimeout((function(){f=s(),m()}),0)}))),{get firstHiddenTime(){return f}}},d=function(e,t){var n,i=v(),o=r(\"FCP\"),f=function(e){\"first-contentful-paint\"===e.name&&(m&&m.disconnect(),e.startTime-1&&e(t)},f=r(\"CLS\",0),s=0,m=[],v=function(e){if(!e.hadRecentInput){var t=m[0],i=m[m.length-1];s&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(s+=e.value,m.push(e)):(s=e.value,m=[e]),s>f.value&&(f.value=s,f.entries=m,n())}},h=a(\"layout-shift\",v);h&&(n=c(i,f,t),o((function(){h.takeRecords().map(v),n(!0)})),u((function(){s=0,l=-1,f=r(\"CLS\",0),n=c(i,f,t)})))},T={passive:!0,capture:!0},y=new Date,g=function(i,r){e||(e=r,t=i,n=new Date,w(removeEventListener),E())},E=function(){if(t>=0&&t1e12?new Date:performance.now())-e.timeStamp;\"pointerdown\"==e.type?function(e,t){var n=function(){g(e,t),r()},i=function(){r()},r=function(){removeEventListener(\"pointerup\",n,T),removeEventListener(\"pointercancel\",i,T)};addEventListener(\"pointerup\",n,T),addEventListener(\"pointercancel\",i,T)}(t,e):g(t,e)}},w=function(e){[\"mousedown\",\"keydown\",\"touchstart\",\"pointerdown\"].forEach((function(t){return e(t,S,T)}))},L=function(n,f){var s,m=v(),d=r(\"FID\"),p=function(e){e.startTimeperformance.now())return;n.entries=[t],e(n)}catch(e){}},\"complete\"===document.readyState?setTimeout(t,0):addEventListener(\"load\",(function(){return setTimeout(t,0)}))};export{h as getCLS,d as getFCP,L as getFID,F as getLCP,P as getTTFB};\n"],"names":["e","t","n","i","r","name","value","delta","entries","id","concat","Date","now","Math","floor","random","a","PerformanceObserver","supportedEntryTypes","includes","self","getEntries","map","observe","type","buffered","o","document","visibilityState","removeEventListener","addEventListener","u","persisted","c","f","s","m","timeStamp","v","setTimeout","firstHiddenTime","d","disconnect","startTime","push","window","performance","getEntriesByName","requestAnimationFrame","p","l","h","hadRecentInput","length","takeRecords","T","passive","capture","y","g","w","E","entryType","target","cancelable","processingStart","forEach","S","L","b","F","once","P","getEntriesByType","timing","max","navigationStart","responseStart","readyState"],"sourceRoot":""}
--------------------------------------------------------------------------------