├── .editorconfig ├── .gitignore ├── COPYING ├── README.md ├── com.github.sdv43.whaler.yml ├── data ├── com.github.sdv43.whaler.appdata.xml.in ├── com.github.sdv43.whaler.desktop.in ├── gresource.xml ├── gschema.xml ├── images │ ├── icons │ │ ├── docker-container-group.svg │ │ └── docker-container.svg │ ├── logo │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 24.png │ │ ├── 32.png │ │ ├── 48.png │ │ └── 64.png │ └── screenshots │ │ ├── screenshot-1.png │ │ ├── screenshot-2.png │ │ └── screenshot-3.png ├── meson.build └── style │ ├── _main.scss │ ├── _tools.scss │ ├── dist │ ├── elementary-dark.css │ └── elementary-light.css │ ├── variants │ ├── elementary-dark.scss │ └── elementary-light.scss │ └── watch.sh ├── meson.build ├── meson └── post_install.py ├── po ├── LINGUAS ├── POTFILES ├── com.github.sdv43.whaler.pot ├── cs.po ├── en.po ├── fr.po ├── it.po ├── meson.build └── ru.po ├── src ├── Application.vala ├── Docker │ ├── ApiClient.vala │ └── ContainerLogWatcher.vala ├── State │ ├── Root.vala │ ├── ScreenDockerContainer.vala │ └── ScreenMain.vala ├── Utils │ ├── Constants.vala.in │ ├── DockerContainer.vala │ ├── Helpers.vala │ ├── HttpClient.vala │ ├── Sorting │ │ ├── SortingInterface.vala │ │ ├── SortingName.vala │ │ ├── SortingStatus.vala │ │ └── SortingType.vala │ └── Theme.vala ├── Widgets │ ├── HeaderBar.vala │ ├── ScreenDockerContainer.vala │ ├── ScreenError.vala │ ├── ScreenMain.vala │ ├── ScreenManager.vala │ ├── Screens │ │ ├── DockerContainer │ │ │ ├── Log.vala │ │ │ ├── LogOutput.vala │ │ │ ├── SideBar.vala │ │ │ ├── SideBarItem.vala │ │ │ ├── SideBarSeparator.vala │ │ │ ├── TopBar.vala │ │ │ └── TopBarActions.vala │ │ └── Main │ │ │ ├── ContainerCard.vala │ │ │ ├── ContainerCardActions.vala │ │ │ ├── ContainersGrid.vala │ │ │ └── ContainersGridFilter.vala │ └── Utils │ │ ├── ConfirmationDialog.vala │ │ ├── ContainerInfoDialog.vala │ │ ├── DockerContainerStatusLabel.vala │ │ └── SettingsDialog.vala └── meson.build ├── uncrustify.cfg └── vapi ├── libcurl.deps └── libcurl.vapi /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.vala] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | tab_width = 4 10 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | build 3 | .flatpak 4 | .flatpak-builder 5 | .vscode 6 | *.glade# 7 | *.glade~ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Whaler 2 | 3 |
4 | Whaler 5 |
6 | 7 | ![List of Docker containers](data/images/screenshots/screenshot-1.png?raw=true) 8 | 9 | ## Description 10 | 11 | Whaler provides basic functionality for managing Docker containers. The app can start and stop both standalone containers and docker-compose applications. Also, it supports container logs viewing. 12 | 13 | The solution is perfect for those who are looking for a simple tool to perform some basic actions. For the app to run correctly, make sure that Docker is installed on your system. 14 | 15 | ## Installation 16 | 17 | [![Get it on AppCenter](https://appcenter.elementary.io/badge.svg)](https://appcenter.elementary.io/com.github.sdv43.whaler) 18 | 19 | Get it from Flathub! 20 | 21 | ## Usage with Podman 22 | 23 | 1. Open Whaler 24 | 2. An error-screen should appear 25 | 3. Click on the "Open Settings" button 26 | 4. Replace the `API socket path` with something like `/run/user/1000/podman/podman.sock` 27 | 28 | ## Building 29 | 30 | You'll need the following dependencies: 31 | * gio-2.0 32 | * gtk+-3.0 33 | * gee-0.8 34 | * gdk-pixbuf-2.0 35 | * json-glib-1.0 36 | * libcurl 37 | * granite 38 | * posix 39 | * meson 40 | * valac 41 | 42 | ### Meson 43 | 44 | In project root: 45 | ``` 46 | meson build --prefix=/usr 47 | cd build 48 | ninja 49 | 50 | sudo ninja install 51 | com.github.sdv43.whaler 52 | ``` 53 | 54 | ### Flatpak 55 | 56 | In project root: 57 | ``` 58 | flatpak-builder --force-clean --install --user build com.github.sdv43.whaler.yml 59 | flatpak run com.github.sdv43.whaler 60 | ``` 61 | -------------------------------------------------------------------------------- /com.github.sdv43.whaler.yml: -------------------------------------------------------------------------------- 1 | app-id: com.github.sdv43.whaler 2 | runtime: io.elementary.Platform 3 | runtime-version: '8' 4 | sdk: io.elementary.Sdk 5 | command: com.github.sdv43.whaler 6 | finish-args: 7 | - '--share=ipc' 8 | - '--socket=fallback-x11' 9 | - '--socket=wayland' 10 | - '--filesystem=/run/docker.sock' 11 | - '--filesystem=xdg-run/docker.sock' 12 | - '--filesystem=/run/podman/podman.sock' 13 | - '--filesystem=xdg-run/podman/podman.sock' 14 | modules: 15 | - name: whaler 16 | buildsystem: meson 17 | sources: 18 | - type: dir 19 | path: . 20 | -------------------------------------------------------------------------------- /data/com.github.sdv43.whaler.appdata.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.github.sdv43.whaler 4 | com.github.sdv43.whaler.desktop 5 | Whaler 6 | Docker Container Management 7 | CC0 8 | GPL-3.0+ 9 | Selivestrov Dmitriy 10 | 11 |

12 | Whaler provides basic functionality for managing Docker containers. 13 | The app can start and stop both standalone containers and docker-compose applications. 14 | Also, it supports viewing container logs. 15 |

16 |

17 | The solution is perfect for those who are looking for a simple tool to perform some basic 18 | actions. 19 | For the app to run correctly, make sure that Docker is installed on your system. 20 |

21 |
22 | 23 | 24 | 25 |
    26 |
  • Update flatpak runtime version
  • 27 |
28 |
29 |
30 | 31 | 32 |
    33 |
  • Add license file
  • 34 |
  • Fix logs reading when tty is enabled
  • 35 |
  • Update flatpak runtime version
  • 36 |
  • Update Italian translation
  • 37 |
38 |
39 |
40 | 41 | 42 |
    43 |
  • Update flatpak runtime version
  • 44 |
  • Fix app crashing with huge logs
  • 45 |
  • Add Czech translation
  • 46 |
47 |
48 |
49 | 50 | 51 |
    52 |
  • Update flatpak runtime version
  • 53 |
54 |
55 |
56 | 57 | 58 |
    59 |
  • Add sorting by container status
  • 60 |
  • Add settings popup
  • 61 |
  • Add Italian translation
  • 62 |
  • Add French translation
  • 63 |
  • Update to io.elementary.Platform 7
  • 64 |
  • Few bug fixes and improvements
  • 65 |
66 |
67 |
68 | 69 | 70 |
    71 |
  • Add container status labels
  • 72 |
  • Show overlay bar for lingering actions
  • 73 |
  • Add "Info" in container context menu
  • 74 |
  • Add "Restart" in container context menu
  • 75 |
  • Add more user-friendly error messages
  • 76 |
  • Update screenshots
  • 77 |
78 |
79 |
80 | 81 | 82 |
    83 |
  • Code refactoring
  • 84 |
  • Add homepage and bugtracker links
  • 85 |
  • Set elementary theme as default
  • 86 |
  • Fix incorrect window position at startup
  • 87 |
  • Fix wrong language choice
  • 88 |
89 |
90 |
91 | 92 |
93 | 94 | 95 | 96 | https://raw.githubusercontent.com/sdv43/whaler/master/data/images/screenshots/screenshot-1.png 97 | Containers list 98 | 99 | 100 | 101 | https://raw.githubusercontent.com/sdv43/whaler/master/data/images/screenshots/screenshot-2.png 102 | Docker compose app 103 | 104 | 105 | 106 | 107 | https://raw.githubusercontent.com/sdv43/whaler/master/data/images/screenshots/screenshot-3.png 108 | Single container app 109 | 110 | 111 | 112 | https://github.com/sdv43/whaler 113 | https://github.com/sdv43/whaler/issues 114 | 115 | 116 | com.github.sdv43.whaler 117 | 118 |
-------------------------------------------------------------------------------- /data/com.github.sdv43.whaler.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Whaler 3 | GenericName=Docker Client 4 | Comment=Manage your Docker containers 5 | Categories=GTK;Development;Utility; 6 | Exec=com.github.sdv43.whaler 7 | Icon=com.github.sdv43.whaler 8 | Terminal=false 9 | Type=Application 10 | Keywords=Docker;Virtualizaton;Development; 11 | -------------------------------------------------------------------------------- /data/gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | images/icons/docker-container-group.svg 8 | 9 | images/icons/docker-container.svg 13 | 14 | style/dist/elementary-light.css 15 | style/dist/elementary-dark.css 16 | 17 | -------------------------------------------------------------------------------- /data/gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | (-1,-1) 6 | Window position 7 | 8 | 9 | (800,600) 10 | Window size 11 | 12 | 13 | false 14 | Whether the window is maximized 15 | 16 | 17 | "" 18 | Search entry value 19 | 20 | 21 | 0 22 | Selected sorting code 23 | 24 | 25 | true 26 | Whether the autoscroll switch is toggled 27 | 28 | 29 | "/run/docker.sock" 30 | Search entry value 31 | 32 | 33 | -------------------------------------------------------------------------------- /data/images/icons/docker-container-group.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/images/icons/docker-container.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/images/logo/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdv43/whaler/e13f063a8beddd18e290fb62aef3303d47805b6d/data/images/logo/128.png -------------------------------------------------------------------------------- /data/images/logo/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdv43/whaler/e13f063a8beddd18e290fb62aef3303d47805b6d/data/images/logo/16.png -------------------------------------------------------------------------------- /data/images/logo/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdv43/whaler/e13f063a8beddd18e290fb62aef3303d47805b6d/data/images/logo/24.png -------------------------------------------------------------------------------- /data/images/logo/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdv43/whaler/e13f063a8beddd18e290fb62aef3303d47805b6d/data/images/logo/32.png -------------------------------------------------------------------------------- /data/images/logo/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdv43/whaler/e13f063a8beddd18e290fb62aef3303d47805b6d/data/images/logo/48.png -------------------------------------------------------------------------------- /data/images/logo/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdv43/whaler/e13f063a8beddd18e290fb62aef3303d47805b6d/data/images/logo/64.png -------------------------------------------------------------------------------- /data/images/screenshots/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdv43/whaler/e13f063a8beddd18e290fb62aef3303d47805b6d/data/images/screenshots/screenshot-1.png -------------------------------------------------------------------------------- /data/images/screenshots/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdv43/whaler/e13f063a8beddd18e290fb62aef3303d47805b6d/data/images/screenshots/screenshot-2.png -------------------------------------------------------------------------------- /data/images/screenshots/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdv43/whaler/e13f063a8beddd18e290fb62aef3303d47805b6d/data/images/screenshots/screenshot-3.png -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | # Gnome resources 2 | gnome = import('gnome') 3 | 4 | gresource = gnome.compile_resources( 5 | 'gresource', 6 | 'gresource.xml', 7 | ) 8 | 9 | # Icons 10 | icon_sizes = ['16', '24', '32', '48', '64', '128'] 11 | 12 | foreach i : icon_sizes 13 | install_data( 14 | 'images/logo/' + i + '.png', 15 | install_dir: get_option('datadir') + '/icons/hicolor/' + i + 'x' + i + '/apps', 16 | rename: meson.project_name() + '.png' 17 | ) 18 | 19 | install_data( 20 | 'images/logo/' + i + '.png', 21 | install_dir: get_option('datadir') + '/icons/hicolor/' + i + 'x' + i + '@2/apps', 22 | rename: meson.project_name() + '.png' 23 | ) 24 | endforeach 25 | 26 | # Settings 27 | install_data ( 28 | 'gschema.xml', 29 | install_dir: get_option('datadir') + '/glib-2.0/schemas', 30 | rename: meson.project_name() + '.gschema.xml' 31 | ) 32 | 33 | # Desktop file 34 | i18n.merge_file( 35 | type: 'desktop', 36 | input: meson.project_name() + '.desktop.in', 37 | output: meson.project_name() + '.desktop', 38 | po_dir: meson.source_root() + '/po', 39 | install_dir: get_option('datadir') + '/applications', 40 | install: true 41 | ) 42 | 43 | # Metainfo file 44 | i18n.merge_file( 45 | input: meson.project_name() + '.appdata.xml.in', 46 | output: meson.project_name() + '.appdata.xml', 47 | po_dir: meson.source_root() + '/po', 48 | install_dir: get_option('datadir') + '/metainfo', 49 | install: true 50 | ) -------------------------------------------------------------------------------- /data/style/_main.scss: -------------------------------------------------------------------------------- 1 | @import "tools"; 2 | 3 | $border-color: rgba(black, 0.25); 4 | $xs: rem(6px); 5 | $sm: rem(12px); 6 | $md: rem(18px); 7 | $lg: rem(24px); 8 | 9 | .titlebar { 10 | background-color: #{"@bg_color"}; 11 | background-image: none; 12 | box-shadow: none; 13 | 14 | .refresh-button { 15 | &.refresh-animation image { 16 | animation-name: rotate; 17 | animation-duration: 500ms; 18 | animation-timing-function: cubic-bezier(0.14, 0.99, 0.97, 1); 19 | animation-iteration-count: 1; 20 | } 21 | } 22 | } 23 | 24 | .confirmation-question { 25 | margin: $sm $lg; 26 | } 27 | 28 | .container-info-dialog { 29 | &-switcher { 30 | margin: $xs 0 $sm 0; 31 | } 32 | 33 | &-tab { 34 | margin: $xs $md 0 0; 35 | } 36 | 37 | &-row { 38 | min-width: rem(360px); 39 | } 40 | 41 | &-label { 42 | margin-right: $xs; 43 | font-weight: bold; 44 | opacity: .8; 45 | } 46 | 47 | &-value { 48 | margin-bottom: $xs; 49 | } 50 | } 51 | 52 | .docker-container-status-label { 53 | border-radius: 20px; 54 | padding: 0.15rem 1rem 0.25rem; 55 | font-weight: 500; 56 | font-size: .95rem; 57 | 58 | &:disabled { 59 | opacity: 0.4; 60 | } 61 | 62 | &.running { 63 | background-color: #{'@accent_color_500'}; 64 | color: white; 65 | } 66 | 67 | &.paused { 68 | background-color: lighter(#{'@accent_color_300'}); 69 | color: #{'@accent_color_700'}; 70 | } 71 | } 72 | 73 | .dialog { 74 | &-settings { 75 | &-title { 76 | padding-bottom: $sm; 77 | } 78 | 79 | &-section { 80 | padding: $xs $md; 81 | } 82 | 83 | &-row { 84 | &-label { 85 | margin-right: $xs; 86 | } 87 | } 88 | 89 | .docker-socket-path { 90 | .button-check { 91 | margin-top: $xs; 92 | 93 | spinner { 94 | margin-left: $sm; 95 | } 96 | } 97 | 98 | .notice { 99 | margin-top: $xs; 100 | padding: $xs 0; 101 | border-radius: $xs; 102 | 103 | .h4 { 104 | padding: 0 0 $xs 0; 105 | } 106 | 107 | &.successfull { 108 | .h4 { 109 | color: #{'@LIME_900'}; 110 | } 111 | } 112 | 113 | &.failed { 114 | color: #{'@STRAWBERRY_900'}; 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | .screen { 122 | &-error { 123 | padding: $lg; 124 | 125 | .alert { 126 | background-color: transparent; 127 | } 128 | } 129 | 130 | &-docker-container { 131 | background-color: #{"@bg_color"}; 132 | 133 | .top-bar { 134 | margin: $lg $lg $md; 135 | 136 | .container-name { 137 | margin-bottom: $xs; 138 | font-size: rem(20px); 139 | } 140 | 141 | .container-image { 142 | padding: 0.125rem 0 0.225rem; 143 | opacity: .7; 144 | } 145 | 146 | .button-main-action { 147 | border-top-right-radius: 0px; 148 | border-bottom-right-radius: 0px; 149 | } 150 | 151 | .button-menu { 152 | border-left: none; 153 | border-top-left-radius: 0px; 154 | border-bottom-left-radius: 0px; 155 | } 156 | } 157 | 158 | .side-bar { 159 | border: 1px solid $border-color; 160 | margin: $lg 0 $lg $lg; 161 | 162 | &-item { 163 | padding: $sm; 164 | } 165 | 166 | &-separator { 167 | padding: $sm $sm $xs $sm; 168 | } 169 | 170 | .image { 171 | margin-top: 0; 172 | } 173 | } 174 | 175 | .log-output { 176 | margin: $lg; 177 | margin-top: 0; 178 | 179 | .terminal { 180 | padding: $sm; 181 | } 182 | } 183 | 184 | .auto-scroll { 185 | opacity: 0.2; 186 | margin: $md; 187 | border-radius: $xs; 188 | padding: $xs; 189 | background-color: #{"@bg_color"}; 190 | transition: opacity ease 150ms; 191 | 192 | &.visible { 193 | opacity: 1; 194 | } 195 | 196 | &-switcher { 197 | margin-left: $xs; 198 | 199 | slider { 200 | min-width: rem(16px); 201 | min-height: rem(16px); 202 | } 203 | } 204 | } 205 | } 206 | 207 | &-main { 208 | background-color: #{"@bg_color"}; 209 | 210 | .docker-containers { 211 | &-filter { 212 | margin: $lg; 213 | } 214 | 215 | &-grid { 216 | margin: $sm; 217 | margin-top: 0; 218 | 219 | .docker-container { 220 | margin: $sm; 221 | border-radius: 3px; 222 | border: 1px solid $border-color; 223 | -gtk-outline-radius: 5px; 224 | padding: $sm; 225 | background-color: bg-color(1); 226 | box-shadow: 227 | outset-highlight("full"), 228 | outset-shadow(2); 229 | 230 | &:focus, 231 | &:selected, 232 | &:active { 233 | @if $color-scheme=="light" { 234 | border-color: #{'@accent_color'}; 235 | } 236 | 237 | color: #{'@selected_fg_color'}; 238 | outline-color: #{'alpha(@accent_color, 0.3)'}; 239 | outline-width: rem(2px); 240 | outline-style: solid; 241 | } 242 | 243 | &-preview-image { 244 | margin-right: $sm; 245 | 246 | &:disabled { 247 | opacity: 0.4; 248 | } 249 | } 250 | 251 | &-name { 252 | margin-bottom: $xs; 253 | } 254 | 255 | &-image { 256 | padding: 0.125rem 0 0.225rem; 257 | opacity: .7; 258 | } 259 | 260 | &-actions { 261 | margin-left: $sm; 262 | } 263 | 264 | .image-button { 265 | margin-left: $xs; 266 | 267 | image { 268 | margin: 0; 269 | } 270 | } 271 | } 272 | } 273 | } 274 | } 275 | } 276 | 277 | @keyframes rotate { 278 | to { 279 | -gtk-icon-transform: rotate(1turn); 280 | } 281 | } -------------------------------------------------------------------------------- /data/style/_tools.scss: -------------------------------------------------------------------------------- 1 | // Source: https://github.com/elementary/stylesheet 2 | 3 | $SILVER_100: #fafafa; 4 | $SILVER_300: #d4d4d4; 5 | $SILVER_500: #abacae; 6 | $SILVER_700: #7e8087; 7 | $SILVER_900: #555761; 8 | 9 | $BLACK_100: #666; 10 | $BLACK_300: #4d4d4d; 11 | $BLACK_500: #333; 12 | $BLACK_700: #1a1a1a; 13 | $BLACK_900: #000; 14 | 15 | @function bg-color($level) { 16 | @if $color-scheme == "light" { 17 | // Inputs 18 | @if $level == 0 { 19 | @return $SILVER_100; 20 | // Views 21 | } @else if $level == 1 { 22 | @return white; 23 | // Background 24 | } @else if $level == 2 { 25 | @return $SILVER_100; 26 | // Sidebars and inline toolbars 27 | } @else if $level == 3 { 28 | @return mix($SILVER_100, $SILVER_300, $weight: 75%); 29 | // Titlebars and toolbars 30 | } @else if $level == 4 { 31 | @return $titlebar-color; 32 | } 33 | } @else if $color-scheme == "dark" { 34 | @if $level == 0 { 35 | @return mix($BLACK_300, $BLACK_500, $weight: 50%); 36 | } @else if $level == 1 { 37 | @return mix($BLACK_300, $BLACK_500, $weight: 25%); 38 | } @else if $level == 2 { 39 | @return $BLACK_500; 40 | } @else if $level == 3 { 41 | @return mix($BLACK_500, $BLACK_700, $weight: 90%); 42 | } @else if $level == 4 { 43 | @return mix($BLACK_500, $BLACK_700, $weight: 45%); 44 | } 45 | } 46 | } 47 | 48 | @function shadow($level) { 49 | @if $color-scheme == "light" { 50 | @if $level == 1 { 51 | @return 52 | 0 1px 3px rgba(black, 0.12), 53 | 0 1px 2px rgba(black, 0.24); 54 | } @else if $level == 2 { 55 | @return 56 | 0 3px 4px rgba(black, 0.15), 57 | 0 3px 3px -3px rgba(black, 0.35); 58 | } @else if $level == 3 { 59 | @return 60 | 0 3px 8px 2px rgba(black, 0.1), 61 | 0 5px 5px -3px rgba(black, 0.4), 62 | 0 8px 5px 1px rgba(black, 0.1); 63 | } @else if $level == 4 { 64 | @return 65 | 0 2px 4px 2px rgba(black, 0.1), 66 | 0 15px 12px -10px rgba(black, 0.4), 67 | 0 8px 14px 4px rgba(black, 0.15); 68 | } 69 | } @else if $color-scheme == "dark" { 70 | @if $level == 1 { 71 | @return 72 | 0 1px 3px rgba(black, 0.42), 73 | 0 1px 2px rgba(black, 0.44); 74 | } @else if $level == 2 { 75 | @return 76 | 0 3px 4px rgba(black, 0.25), 77 | 0 3px 3px -3px rgba(black, 0.45); 78 | } @else if $level == 3 { 79 | @return 80 | 0 3px 8px 2px rgba(black, 0.2), 81 | 0 5px 5px -3px rgba(black, 0.5), 82 | 0 8px 5px 1px rgba(black, 0.2); 83 | } @else if $level == 4 { 84 | @return 85 | 0 2px 4px 2px rgba(black, 0.2), 86 | 0 15px 12px -10px rgba(black, 0.5), 87 | 0 8px 14px 4px rgba(black, 0.25); 88 | } 89 | } 90 | } 91 | 92 | @function inset-shadow($state: "") { 93 | @if $state == "disabled" { 94 | @return 95 | 0 1px 0 0 #{'alpha(@highlight_color, 0.3)'}, 96 | inset 0 1px 1px rgba(black, 0.05); 97 | } 98 | 99 | @return 100 | 0 1px 0 0 #{'alpha(@highlight_color, 0.3)'}, 101 | inset 0 1px 1px rgba(black, 0.05), 102 | inset 0 0 1px 1px rgba(black, 0.05); 103 | } 104 | 105 | @function outset-highlight($sides: "full") { 106 | $highlight: 107 | inset 1px 0 0 0 #{'alpha(@highlight_color, 0.07)'}, 108 | inset -1px 0 0 0 #{'alpha(@highlight_color, 0.07)'}; 109 | 110 | @if $sides == "top" or $sides == "full" { 111 | $highlight: 112 | inset 0 1px 0 0 #{'alpha(@highlight_color, 0.3)'}, 113 | $highlight; 114 | } 115 | 116 | @if $sides == "bottom" or $sides == "full" { 117 | $highlight: 118 | inset 0 -1px 0 0 #{'alpha(@highlight_color, 0.2)'}, 119 | $highlight; 120 | } 121 | 122 | @return $highlight; 123 | } 124 | 125 | @function outset-shadow($level) { 126 | @if $level == 1 { 127 | @return 0 1px 1px rgba(black, 0.05); 128 | } @else if $level == 2 { 129 | @return 130 | 0 1px 1px rgba(black, 0.07), 131 | 0 1px 2px rgba(black, 0.08); 132 | } @else if $level == 3 { 133 | @return 134 | 0 1px 3px rgba(black, 0.12), 135 | 0 1px 2px rgba(black, 0.24); 136 | } 137 | } 138 | 139 | @function rem($pixels, $text-size: 9pt) { 140 | @if (unitless($pixels)) { 141 | $pixels: $pixels * 1px; 142 | } 143 | 144 | @if (unitless($text-size)) { 145 | $text-size: $text-size * 1px; 146 | } 147 | 148 | @if ($pixels > 0) { 149 | // Workaround GTK clamping instead of rounding up 150 | @return calc($pixels / $text-size * 1rem) + 0.000000001rem; 151 | } @else { 152 | // Workaround GTK clamping instead of rounding up 153 | @return calc($pixels / $text-size * 1rem) - 0.000000001rem; 154 | } 155 | } 156 | 157 | // Background for elements with outset style like headerbars, buttons, checkboxes, etc 158 | %outset-background { 159 | background-image: 160 | linear-gradient( 161 | to bottom, 162 | #{'alpha(@highlight_color, 0.2)'}, 163 | rgba(white, 0) 164 | ); 165 | 166 | &:backdrop { 167 | background-image: 168 | linear-gradient( 169 | to bottom, 170 | #{'alpha(@highlight_color, 0.35)'}, 171 | #{'alpha(@highlight_color, 0.3)'} 172 | ); 173 | } 174 | } -------------------------------------------------------------------------------- /data/style/dist/elementary-dark.css: -------------------------------------------------------------------------------- 1 | .titlebar { 2 | background-color: @bg_color; 3 | background-image: none; 4 | box-shadow: none; 5 | } 6 | .titlebar .refresh-button.refresh-animation image { 7 | animation-name: rotate; 8 | animation-duration: 500ms; 9 | animation-timing-function: cubic-bezier(0.14, 0.99, 0.97, 1); 10 | animation-iteration-count: 1; 11 | } 12 | 13 | .confirmation-question { 14 | margin: 1.000000001rem 2.000000001rem; 15 | } 16 | 17 | .container-info-dialog-switcher { 18 | margin: 0.500000001rem 0 1.000000001rem 0; 19 | } 20 | .container-info-dialog-tab { 21 | margin: 0.500000001rem 1.500000001rem 0 0; 22 | } 23 | .container-info-dialog-row { 24 | min-width: 30.000000001rem; 25 | } 26 | .container-info-dialog-label { 27 | margin-right: 0.500000001rem; 28 | font-weight: bold; 29 | opacity: 0.8; 30 | } 31 | .container-info-dialog-value { 32 | margin-bottom: 0.500000001rem; 33 | } 34 | 35 | .docker-container-status-label { 36 | border-radius: 20px; 37 | padding: 0.15rem 1rem 0.25rem; 38 | font-weight: 500; 39 | font-size: 0.95rem; 40 | } 41 | .docker-container-status-label:disabled { 42 | opacity: 0.4; 43 | } 44 | .docker-container-status-label.running { 45 | background-color: @accent_color_500; 46 | color: white; 47 | } 48 | .docker-container-status-label.paused { 49 | background-color: lighter(@accent_color_300); 50 | color: @accent_color_700; 51 | } 52 | 53 | .dialog-settings-title { 54 | padding-bottom: 1.000000001rem; 55 | } 56 | .dialog-settings-section { 57 | padding: 0.500000001rem 1.500000001rem; 58 | } 59 | .dialog-settings-row-label { 60 | margin-right: 0.500000001rem; 61 | } 62 | .dialog-settings .docker-socket-path .button-check { 63 | margin-top: 0.500000001rem; 64 | } 65 | .dialog-settings .docker-socket-path .button-check spinner { 66 | margin-left: 1.000000001rem; 67 | } 68 | .dialog-settings .docker-socket-path .notice { 69 | margin-top: 0.500000001rem; 70 | padding: 0.500000001rem 0; 71 | border-radius: 0.500000001rem; 72 | } 73 | .dialog-settings .docker-socket-path .notice .h4 { 74 | padding: 0 0 0.500000001rem 0; 75 | } 76 | .dialog-settings .docker-socket-path .notice.successfull .h4 { 77 | color: @LIME_900; 78 | } 79 | .dialog-settings .docker-socket-path .notice.failed { 80 | color: @STRAWBERRY_900; 81 | } 82 | 83 | .screen-error { 84 | padding: 2.000000001rem; 85 | } 86 | .screen-error .alert { 87 | background-color: transparent; 88 | } 89 | .screen-docker-container { 90 | background-color: @bg_color; 91 | } 92 | .screen-docker-container .top-bar { 93 | margin: 2.000000001rem 2.000000001rem 1.500000001rem; 94 | } 95 | .screen-docker-container .top-bar .container-name { 96 | margin-bottom: 0.500000001rem; 97 | font-size: 1.6666666677rem; 98 | } 99 | .screen-docker-container .top-bar .container-image { 100 | padding: 0.125rem 0 0.225rem; 101 | opacity: 0.7; 102 | } 103 | .screen-docker-container .top-bar .button-main-action { 104 | border-top-right-radius: 0px; 105 | border-bottom-right-radius: 0px; 106 | } 107 | .screen-docker-container .top-bar .button-menu { 108 | border-left: none; 109 | border-top-left-radius: 0px; 110 | border-bottom-left-radius: 0px; 111 | } 112 | .screen-docker-container .side-bar { 113 | border: 1px solid rgba(0, 0, 0, 0.25); 114 | margin: 2.000000001rem 0 2.000000001rem 2.000000001rem; 115 | } 116 | .screen-docker-container .side-bar-item { 117 | padding: 1.000000001rem; 118 | } 119 | .screen-docker-container .side-bar-separator { 120 | padding: 1.000000001rem 1.000000001rem 0.500000001rem 1.000000001rem; 121 | } 122 | .screen-docker-container .side-bar .image { 123 | margin-top: 0; 124 | } 125 | .screen-docker-container .log-output { 126 | margin: 2.000000001rem; 127 | margin-top: 0; 128 | } 129 | .screen-docker-container .log-output .terminal { 130 | padding: 1.000000001rem; 131 | } 132 | .screen-docker-container .auto-scroll { 133 | opacity: 0.2; 134 | margin: 1.500000001rem; 135 | border-radius: 0.500000001rem; 136 | padding: 0.500000001rem; 137 | background-color: @bg_color; 138 | transition: opacity ease 150ms; 139 | } 140 | .screen-docker-container .auto-scroll.visible { 141 | opacity: 1; 142 | } 143 | .screen-docker-container .auto-scroll-switcher { 144 | margin-left: 0.500000001rem; 145 | } 146 | .screen-docker-container .auto-scroll-switcher slider { 147 | min-width: 1.3333333343rem; 148 | min-height: 1.3333333343rem; 149 | } 150 | .screen-main { 151 | background-color: @bg_color; 152 | } 153 | .screen-main .docker-containers-filter { 154 | margin: 2.000000001rem; 155 | } 156 | .screen-main .docker-containers-grid { 157 | margin: 1.000000001rem; 158 | margin-top: 0; 159 | } 160 | .screen-main .docker-containers-grid .docker-container { 161 | margin: 1.000000001rem; 162 | border-radius: 3px; 163 | border: 1px solid rgba(0, 0, 0, 0.25); 164 | -gtk-outline-radius: 5px; 165 | padding: 1.000000001rem; 166 | background-color: #3a3a3a; 167 | box-shadow: inset 0 -1px 0 0 alpha(@highlight_color, 0.2), inset 0 1px 0 0 alpha(@highlight_color, 0.3), inset 1px 0 0 0 alpha(@highlight_color, 0.07), inset -1px 0 0 0 alpha(@highlight_color, 0.07), 0 1px 1px rgba(0, 0, 0, 0.07), 0 1px 2px rgba(0, 0, 0, 0.08); 168 | } 169 | .screen-main .docker-containers-grid .docker-container:focus, .screen-main .docker-containers-grid .docker-container:selected, .screen-main .docker-containers-grid .docker-container:active { 170 | color: @selected_fg_color; 171 | outline-color: alpha(@accent_color, 0.3); 172 | outline-width: 0.1666666677rem; 173 | outline-style: solid; 174 | } 175 | .screen-main .docker-containers-grid .docker-container-preview-image { 176 | margin-right: 1.000000001rem; 177 | } 178 | .screen-main .docker-containers-grid .docker-container-preview-image:disabled { 179 | opacity: 0.4; 180 | } 181 | .screen-main .docker-containers-grid .docker-container-name { 182 | margin-bottom: 0.500000001rem; 183 | } 184 | .screen-main .docker-containers-grid .docker-container-image { 185 | padding: 0.125rem 0 0.225rem; 186 | opacity: 0.7; 187 | } 188 | .screen-main .docker-containers-grid .docker-container-actions { 189 | margin-left: 1.000000001rem; 190 | } 191 | .screen-main .docker-containers-grid .docker-container .image-button { 192 | margin-left: 0.500000001rem; 193 | } 194 | .screen-main .docker-containers-grid .docker-container .image-button image { 195 | margin: 0; 196 | } 197 | 198 | @keyframes rotate { 199 | to { 200 | -gtk-icon-transform: rotate(1turn); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /data/style/dist/elementary-light.css: -------------------------------------------------------------------------------- 1 | .titlebar { 2 | background-color: @bg_color; 3 | background-image: none; 4 | box-shadow: none; 5 | } 6 | .titlebar .refresh-button.refresh-animation image { 7 | animation-name: rotate; 8 | animation-duration: 500ms; 9 | animation-timing-function: cubic-bezier(0.14, 0.99, 0.97, 1); 10 | animation-iteration-count: 1; 11 | } 12 | 13 | .confirmation-question { 14 | margin: 1.000000001rem 2.000000001rem; 15 | } 16 | 17 | .container-info-dialog-switcher { 18 | margin: 0.500000001rem 0 1.000000001rem 0; 19 | } 20 | .container-info-dialog-tab { 21 | margin: 0.500000001rem 1.500000001rem 0 0; 22 | } 23 | .container-info-dialog-row { 24 | min-width: 30.000000001rem; 25 | } 26 | .container-info-dialog-label { 27 | margin-right: 0.500000001rem; 28 | font-weight: bold; 29 | opacity: 0.8; 30 | } 31 | .container-info-dialog-value { 32 | margin-bottom: 0.500000001rem; 33 | } 34 | 35 | .docker-container-status-label { 36 | border-radius: 20px; 37 | padding: 0.15rem 1rem 0.25rem; 38 | font-weight: 500; 39 | font-size: 0.95rem; 40 | } 41 | .docker-container-status-label:disabled { 42 | opacity: 0.4; 43 | } 44 | .docker-container-status-label.running { 45 | background-color: @accent_color_500; 46 | color: white; 47 | } 48 | .docker-container-status-label.paused { 49 | background-color: lighter(@accent_color_300); 50 | color: @accent_color_700; 51 | } 52 | 53 | .dialog-settings-title { 54 | padding-bottom: 1.000000001rem; 55 | } 56 | .dialog-settings-section { 57 | padding: 0.500000001rem 1.500000001rem; 58 | } 59 | .dialog-settings-row-label { 60 | margin-right: 0.500000001rem; 61 | } 62 | .dialog-settings .docker-socket-path .button-check { 63 | margin-top: 0.500000001rem; 64 | } 65 | .dialog-settings .docker-socket-path .button-check spinner { 66 | margin-left: 1.000000001rem; 67 | } 68 | .dialog-settings .docker-socket-path .notice { 69 | margin-top: 0.500000001rem; 70 | padding: 0.500000001rem 0; 71 | border-radius: 0.500000001rem; 72 | } 73 | .dialog-settings .docker-socket-path .notice .h4 { 74 | padding: 0 0 0.500000001rem 0; 75 | } 76 | .dialog-settings .docker-socket-path .notice.successfull .h4 { 77 | color: @LIME_900; 78 | } 79 | .dialog-settings .docker-socket-path .notice.failed { 80 | color: @STRAWBERRY_900; 81 | } 82 | 83 | .screen-error { 84 | padding: 2.000000001rem; 85 | } 86 | .screen-error .alert { 87 | background-color: transparent; 88 | } 89 | .screen-docker-container { 90 | background-color: @bg_color; 91 | } 92 | .screen-docker-container .top-bar { 93 | margin: 2.000000001rem 2.000000001rem 1.500000001rem; 94 | } 95 | .screen-docker-container .top-bar .container-name { 96 | margin-bottom: 0.500000001rem; 97 | font-size: 1.6666666677rem; 98 | } 99 | .screen-docker-container .top-bar .container-image { 100 | padding: 0.125rem 0 0.225rem; 101 | opacity: 0.7; 102 | } 103 | .screen-docker-container .top-bar .button-main-action { 104 | border-top-right-radius: 0px; 105 | border-bottom-right-radius: 0px; 106 | } 107 | .screen-docker-container .top-bar .button-menu { 108 | border-left: none; 109 | border-top-left-radius: 0px; 110 | border-bottom-left-radius: 0px; 111 | } 112 | .screen-docker-container .side-bar { 113 | border: 1px solid rgba(0, 0, 0, 0.25); 114 | margin: 2.000000001rem 0 2.000000001rem 2.000000001rem; 115 | } 116 | .screen-docker-container .side-bar-item { 117 | padding: 1.000000001rem; 118 | } 119 | .screen-docker-container .side-bar-separator { 120 | padding: 1.000000001rem 1.000000001rem 0.500000001rem 1.000000001rem; 121 | } 122 | .screen-docker-container .side-bar .image { 123 | margin-top: 0; 124 | } 125 | .screen-docker-container .log-output { 126 | margin: 2.000000001rem; 127 | margin-top: 0; 128 | } 129 | .screen-docker-container .log-output .terminal { 130 | padding: 1.000000001rem; 131 | } 132 | .screen-docker-container .auto-scroll { 133 | opacity: 0.2; 134 | margin: 1.500000001rem; 135 | border-radius: 0.500000001rem; 136 | padding: 0.500000001rem; 137 | background-color: @bg_color; 138 | transition: opacity ease 150ms; 139 | } 140 | .screen-docker-container .auto-scroll.visible { 141 | opacity: 1; 142 | } 143 | .screen-docker-container .auto-scroll-switcher { 144 | margin-left: 0.500000001rem; 145 | } 146 | .screen-docker-container .auto-scroll-switcher slider { 147 | min-width: 1.3333333343rem; 148 | min-height: 1.3333333343rem; 149 | } 150 | .screen-main { 151 | background-color: @bg_color; 152 | } 153 | .screen-main .docker-containers-filter { 154 | margin: 2.000000001rem; 155 | } 156 | .screen-main .docker-containers-grid { 157 | margin: 1.000000001rem; 158 | margin-top: 0; 159 | } 160 | .screen-main .docker-containers-grid .docker-container { 161 | margin: 1.000000001rem; 162 | border-radius: 3px; 163 | border: 1px solid rgba(0, 0, 0, 0.25); 164 | -gtk-outline-radius: 5px; 165 | padding: 1.000000001rem; 166 | background-color: white; 167 | box-shadow: inset 0 -1px 0 0 alpha(@highlight_color, 0.2), inset 0 1px 0 0 alpha(@highlight_color, 0.3), inset 1px 0 0 0 alpha(@highlight_color, 0.07), inset -1px 0 0 0 alpha(@highlight_color, 0.07), 0 1px 1px rgba(0, 0, 0, 0.07), 0 1px 2px rgba(0, 0, 0, 0.08); 168 | } 169 | .screen-main .docker-containers-grid .docker-container:focus, .screen-main .docker-containers-grid .docker-container:selected, .screen-main .docker-containers-grid .docker-container:active { 170 | border-color: @accent_color; 171 | color: @selected_fg_color; 172 | outline-color: alpha(@accent_color, 0.3); 173 | outline-width: 0.1666666677rem; 174 | outline-style: solid; 175 | } 176 | .screen-main .docker-containers-grid .docker-container-preview-image { 177 | margin-right: 1.000000001rem; 178 | } 179 | .screen-main .docker-containers-grid .docker-container-preview-image:disabled { 180 | opacity: 0.4; 181 | } 182 | .screen-main .docker-containers-grid .docker-container-name { 183 | margin-bottom: 0.500000001rem; 184 | } 185 | .screen-main .docker-containers-grid .docker-container-image { 186 | padding: 0.125rem 0 0.225rem; 187 | opacity: 0.7; 188 | } 189 | .screen-main .docker-containers-grid .docker-container-actions { 190 | margin-left: 1.000000001rem; 191 | } 192 | .screen-main .docker-containers-grid .docker-container .image-button { 193 | margin-left: 0.500000001rem; 194 | } 195 | .screen-main .docker-containers-grid .docker-container .image-button image { 196 | margin: 0; 197 | } 198 | 199 | @keyframes rotate { 200 | to { 201 | -gtk-icon-transform: rotate(1turn); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /data/style/variants/elementary-dark.scss: -------------------------------------------------------------------------------- 1 | $color-scheme: "dark"; 2 | 3 | @import "./../main"; -------------------------------------------------------------------------------- /data/style/variants/elementary-light.scss: -------------------------------------------------------------------------------- 1 | $color-scheme: "light"; 2 | 3 | @import "./../main"; -------------------------------------------------------------------------------- /data/style/watch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" 4 | 5 | sass \ 6 | ./variants/elementary-dark.scss:./dist/elementary-dark.css \ 7 | ./variants/elementary-light.scss:./dist/elementary-light.css \ 8 | --no-source-map \ -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'com.github.sdv43.whaler', 'vala', 'c', 3 | meson_version : '>= 0.49', 4 | version: '1.2.4' 5 | ) 6 | 7 | # 8 | i18n = import('i18n') 9 | vapi_dir = meson.current_source_dir() / 'vapi' 10 | 11 | add_global_arguments('-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name()), language: 'c') 12 | add_project_arguments(['--vapidir', vapi_dir], language: 'vala') 13 | 14 | # 15 | subdir('po') 16 | subdir('data') 17 | subdir('src') 18 | 19 | # 20 | executable( 21 | meson.project_name(), 22 | gresource, 23 | constants, 24 | sources, 25 | dependencies: [ 26 | dependency('gio-2.0'), 27 | dependency('gtk+-3.0'), 28 | dependency('gee-0.8'), 29 | dependency('gdk-pixbuf-2.0'), 30 | dependency('json-glib-1.0'), 31 | dependency('granite'), 32 | meson.get_compiler('vala').find_library('posix'), 33 | meson.get_compiler('vala').find_library('libcurl', dirs: vapi_dir), 34 | meson.get_compiler('c').find_library('libcurl', dirs: vapi_dir), 35 | ], 36 | install: true 37 | ) 38 | 39 | meson.add_install_script('meson/post_install.py') 40 | -------------------------------------------------------------------------------- /meson/post_install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import subprocess 5 | 6 | schemadir = os.path.join(os.environ['MESON_INSTALL_PREFIX'], 'share', 'glib-2.0', 'schemas') 7 | 8 | if not os.environ.get('DESTDIR'): 9 | print('Compiling gsettings schemas...') 10 | subprocess.call(['glib-compile-schemas', schemadir]) -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | cs 2 | en 3 | fr 4 | it 5 | ru 6 | -------------------------------------------------------------------------------- /po/POTFILES: -------------------------------------------------------------------------------- 1 | data/com.github.sdv43.whaler.desktop.in 2 | data/com.github.sdv43.whaler.appdata.xml.in 3 | src/Utils/Sorting/SortingName.vala 4 | src/Utils/Sorting/SortingType.vala 5 | src/Widgets/Utils/SettingsDialog.vala 6 | src/Widgets/Utils/DockerContainerStatusLabel.vala 7 | src/Widgets/Utils/ContainerInfoDialog.vala 8 | src/State/Root.vala 9 | src/Widgets/HeaderBar.vala 10 | src/Widgets/ScreenError.vala 11 | src/Widgets/Screens/DockerContainer/Log.vala 12 | src/Widgets/Screens/DockerContainer/LogOutput.vala 13 | src/Widgets/Screens/DockerContainer/TopBar.vala 14 | src/Widgets/Screens/DockerContainer/TopBarActions.vala 15 | src/Widgets/Screens/Main/ContainerCard.vala 16 | src/Widgets/Screens/Main/ContainerCardActions.vala 17 | src/Widgets/Screens/Main/ContainersGrid.vala 18 | src/Widgets/Screens/Main/ContainersGridFilter.vala 19 | -------------------------------------------------------------------------------- /po/com.github.sdv43.whaler.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the com.github.sdv43.whaler package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: com.github.sdv43.whaler\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2022-07-04 08:24+0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: data/com.github.sdv43.whaler.desktop.in:5 21 | msgid "Manage your Docker containers" 22 | msgstr "" 23 | 24 | #: data/com.github.sdv43.whaler.desktop.in:11 25 | msgid "Docker;Virtualizaton;Development;" 26 | msgstr "" 27 | 28 | #: data/com.github.sdv43.whaler.appdata.xml.in:5 29 | msgid "Docker Container Management" 30 | msgstr "" 31 | 32 | #: data/com.github.sdv43.whaler.appdata.xml.in:10 33 | msgid "" 34 | "Whaler provides basic functionality for managing Docker containers. The app " 35 | "can start and stop both standalone containers and docker-compose " 36 | "applications. Also, it supports viewing container logs." 37 | msgstr "" 38 | 39 | #: data/com.github.sdv43.whaler.appdata.xml.in:15 40 | msgid "" 41 | "The solution is perfect for those who are looking for a simple tool to " 42 | "perform some basic actions. For the app to run correctly, make sure that " 43 | "Docker is installed on your system." 44 | msgstr "" 45 | 46 | #: src/Utils/Sorting/SortingName.vala:12 47 | #: src/Widgets/Utils/ContainerInfoDialog.vala:52 48 | msgid "Name" 49 | msgstr "" 50 | 51 | #: src/Utils/Sorting/SortingType.vala:10 52 | msgid "Type" 53 | msgstr "" 54 | 55 | #: src/Widgets/Utils/SettingsDialog.vala:11 56 | #: src/Widgets/Utils/ContainerInfoDialog.vala:13 57 | msgid "Close" 58 | msgstr "" 59 | 60 | #: src/Widgets/Utils/SettingsDialog.vala:26 61 | msgid "Settings" 62 | msgstr "" 63 | 64 | #: src/Widgets/Utils/SettingsDialog.vala:41 65 | msgid "API socket path:" 66 | msgstr "" 67 | 68 | #: src/Widgets/Utils/SettingsDialog.vala:104 69 | msgid "Check connection" 70 | msgstr "" 71 | 72 | #: src/Widgets/Utils/SettingsDialog.vala:124 73 | msgid "Success" 74 | msgstr "" 75 | 76 | #: src/Widgets/Utils/SettingsDialog.vala:124 77 | msgid "Error" 78 | msgstr "" 79 | 80 | #: src/Widgets/Utils/SettingsDialog.vala:141 81 | #, c-format 82 | msgid "Incorrect socket path \"%s\"" 83 | msgstr "" 84 | 85 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:11 86 | msgid "Running" 87 | msgstr "" 88 | 89 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:16 90 | msgid "Paused" 91 | msgstr "" 92 | 93 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:21 94 | msgid "Stopped" 95 | msgstr "" 96 | 97 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:26 98 | msgid "Unknown" 99 | msgstr "" 100 | 101 | #: src/Widgets/Utils/ContainerInfoDialog.vala:53 102 | msgid "Image" 103 | msgstr "" 104 | 105 | #: src/Widgets/Utils/ContainerInfoDialog.vala:54 106 | msgid "Status" 107 | msgstr "" 108 | 109 | #: src/Widgets/Utils/ContainerInfoDialog.vala:57 110 | msgid "Ports" 111 | msgstr "" 112 | 113 | #: src/Widgets/Utils/ContainerInfoDialog.vala:61 114 | msgid "Binds" 115 | msgstr "" 116 | 117 | #: src/Widgets/Utils/ContainerInfoDialog.vala:65 118 | msgid "Env" 119 | msgstr "" 120 | 121 | #: src/Widgets/HeaderBar.vala:15 122 | msgid "Back" 123 | msgstr "" 124 | 125 | #: src/Widgets/HeaderBar.vala:40 126 | msgid "Update docker container list" 127 | msgstr "" 128 | 129 | #: src/Widgets/HeaderBar.vala:87 src/Widgets/ScreenError.vala:56 130 | msgid "Open settings" 131 | msgstr "" 132 | 133 | #: src/Widgets/ScreenError.vala:33 134 | msgid "" 135 | "It looks like Docker requires root rights to use it. Thus, the application " 136 | "cannot connect to Docker Engine API. Find out how to run docker without root " 137 | "rights in Docker Manuals, otherwise the application cannot work correctly. Or " 139 | "check your socket path to Docker API in Settings." 140 | msgstr "" 141 | 142 | #: src/Widgets/ScreenError.vala:42 143 | msgid "" 144 | "It looks like Docker is not installed on your system. To find out how to " 145 | "install it, see Docker " 146 | "Manuals. Or check your socket path to Docker API in Settings." 147 | msgstr "" 148 | 149 | #: src/Widgets/ScreenError.vala:50 150 | msgid "The app cannot connect to Docker API" 151 | msgstr "" 152 | 153 | #: src/Widgets/Screens/DockerContainer/Log.vala:39 154 | msgid "Autoscroll" 155 | msgstr "" 156 | 157 | #: src/Widgets/Screens/DockerContainer/Log.vala:51 158 | msgid "Enable autoscroll to bottom border" 159 | msgstr "" 160 | 161 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:19 162 | msgid "Start" 163 | msgstr "" 164 | 165 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:23 166 | msgid "Stop" 167 | msgstr "" 168 | 169 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:25 170 | msgid "Unpause" 171 | msgstr "" 172 | 173 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:56 174 | msgid "Pause" 175 | msgstr "" 176 | 177 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:59 178 | msgid "Container pause error" 179 | msgstr "" 180 | 181 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:74 182 | msgid "Restart" 183 | msgstr "" 184 | 185 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:76 186 | msgid "Container restart error" 187 | msgstr "" 188 | 189 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:79 190 | msgid "Restarting container" 191 | msgstr "" 192 | 193 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:94 194 | msgid "Remove" 195 | msgstr "" 196 | 197 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:97 198 | msgid "Do you really want to remove container?" 199 | msgstr "" 200 | 201 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:98 202 | msgid "Yes, remove" 203 | msgstr "" 204 | 205 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:99 206 | msgid "Cancel" 207 | msgstr "" 208 | 209 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:105 210 | msgid "Container remove error" 211 | msgstr "" 212 | 213 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:107 214 | msgid "Removing container" 215 | msgstr "" 216 | 217 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:127 218 | msgid "Info" 219 | msgstr "" 220 | 221 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:129 222 | msgid "Cannot get information" 223 | msgstr "" 224 | 225 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:152 226 | msgid "Container action error" 227 | msgstr "" 228 | 229 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:157 230 | msgid "Container stop error" 231 | msgstr "" 232 | 233 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:158 234 | msgid "Stopping container" 235 | msgstr "" 236 | 237 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:163 238 | msgid "Container start error" 239 | msgstr "" 240 | 241 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:164 242 | msgid "Starting container" 243 | msgstr "" 244 | 245 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:169 246 | msgid "Container unpause error" 247 | msgstr "" 248 | 249 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:174 250 | msgid "Container state is unknown" 251 | msgstr "" 252 | 253 | #: src/Widgets/Screens/Main/ContainersGrid.vala:81 254 | msgid "No containers" 255 | msgstr "" 256 | 257 | #: src/Widgets/Screens/Main/ContainersGridFilter.vala:53 258 | msgid "Sort by:" 259 | msgstr "" 260 | -------------------------------------------------------------------------------- /po/cs.po: -------------------------------------------------------------------------------- 1 | # Czech translation for Whaler 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the com.github.sdv43.whaler package. 4 | # Amerey.eu , 2023. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: com.github.sdv43.whaler\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2022-07-04 08:24+0300\n" 11 | "PO-Revision-Date: 2023-04-22 17:53+0200\n" 12 | "Last-Translator: Amerey.eu \n" 13 | "Language-Team: \n" 14 | "Language: cs\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2);\n" 19 | "X-Generator: Poedit 3.1.1\n" 20 | 21 | #: data/com.github.sdv43.whaler.desktop.in:5 22 | msgid "Manage your Docker containers" 23 | msgstr "Spravujte své kontejnery Docker" 24 | 25 | #: data/com.github.sdv43.whaler.desktop.in:11 26 | msgid "Docker;Virtualizaton;Development;" 27 | msgstr "Docker;Virtualizaton;Development;" 28 | 29 | #: data/com.github.sdv43.whaler.appdata.xml.in:5 30 | msgid "Docker Container Management" 31 | msgstr "Docker Container Management" 32 | 33 | #: data/com.github.sdv43.whaler.appdata.xml.in:10 34 | msgid "" 35 | "Whaler provides basic functionality for managing Docker containers. The app " 36 | "can start and stop both standalone containers and docker-compose " 37 | "applications. Also, it supports viewing container logs." 38 | msgstr "" 39 | "Whaler poskytuje základní funkce pro správu kontejnerů Docker. Aplikace může " 40 | "spouštět a zastavovat jak samostatné kontejnery, tak aplikace složené z " 41 | "dockeru. Podporuje také prohlížení protokolů kontejnerů." 42 | 43 | #: data/com.github.sdv43.whaler.appdata.xml.in:15 44 | msgid "" 45 | "The solution is perfect for those who are looking for a simple tool to " 46 | "perform some basic actions. For the app to run correctly, make sure that " 47 | "Docker is installed on your system." 48 | msgstr "" 49 | "Toto řešení je ideální pro ty, kteří hledají jednoduchý nástroj k provádění " 50 | "některých základních akcí. Aby aplikace fungovala správně, ujistěte se, že " 51 | "je ve vašem systému nainstalován Docker." 52 | 53 | #: src/Utils/Sorting/SortingName.vala:12 54 | #: src/Widgets/Utils/ContainerInfoDialog.vala:52 55 | msgid "Name" 56 | msgstr "Název" 57 | 58 | #: src/Utils/Sorting/SortingType.vala:10 59 | msgid "Type" 60 | msgstr "Typ" 61 | 62 | #: src/Widgets/Utils/SettingsDialog.vala:11 63 | #: src/Widgets/Utils/ContainerInfoDialog.vala:13 64 | msgid "Close" 65 | msgstr "Zavřít" 66 | 67 | #: src/Widgets/Utils/SettingsDialog.vala:26 68 | msgid "Settings" 69 | msgstr "Nastavení" 70 | 71 | #: src/Widgets/Utils/SettingsDialog.vala:41 72 | msgid "API socket path:" 73 | msgstr "Cesta k soketu rozhraní API:" 74 | 75 | #: src/Widgets/Utils/SettingsDialog.vala:104 76 | msgid "Check connection" 77 | msgstr "Zkontrolovat připojení" 78 | 79 | #: src/Widgets/Utils/SettingsDialog.vala:124 80 | msgid "Success" 81 | msgstr "Úspěch" 82 | 83 | #: src/Widgets/Utils/SettingsDialog.vala:124 84 | msgid "Error" 85 | msgstr "Chyba" 86 | 87 | #: src/Widgets/Utils/SettingsDialog.vala:141 88 | #, c-format 89 | msgid "Incorrect socket path \"%s\"" 90 | msgstr "Nesprávná cesta soketu \"%s\"" 91 | 92 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:11 93 | msgid "Running" 94 | msgstr "Běží" 95 | 96 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:16 97 | msgid "Paused" 98 | msgstr "Pozastaven" 99 | 100 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:21 101 | msgid "Stopped" 102 | msgstr "Zastaven" 103 | 104 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:26 105 | msgid "Unknown" 106 | msgstr "Neznámý" 107 | 108 | #: src/Widgets/Utils/ContainerInfoDialog.vala:53 109 | msgid "Image" 110 | msgstr "Obrázek" 111 | 112 | #: src/Widgets/Utils/ContainerInfoDialog.vala:54 113 | msgid "Status" 114 | msgstr "Stav" 115 | 116 | #: src/Widgets/Utils/ContainerInfoDialog.vala:57 117 | msgid "Ports" 118 | msgstr "Porty" 119 | 120 | #: src/Widgets/Utils/ContainerInfoDialog.vala:61 121 | msgid "Binds" 122 | msgstr "Propojení" 123 | 124 | #: src/Widgets/Utils/ContainerInfoDialog.vala:65 125 | msgid "Env" 126 | msgstr "Prostředí" 127 | 128 | #: src/Widgets/HeaderBar.vala:15 129 | msgid "Back" 130 | msgstr "Zpět" 131 | 132 | #: src/Widgets/HeaderBar.vala:40 133 | msgid "Update docker container list" 134 | msgstr "Aktualizovat seznam kontejnerů" 135 | 136 | #: src/Widgets/HeaderBar.vala:87 src/Widgets/ScreenError.vala:56 137 | msgid "Open settings" 138 | msgstr "Otevřít nastavení" 139 | 140 | #: src/Widgets/ScreenError.vala:33 141 | msgid "" 142 | "It looks like Docker requires root rights to use it. Thus, the application " 143 | "cannot connect to Docker Engine API. Find out how to run docker without root " 144 | "rights in Docker Manuals, otherwise the application cannot work correctly. Or " 146 | "check your socket path to Docker API in Settings." 147 | msgstr "" 148 | "Vypadá to, že Docker vyžaduje práva root, aby jej mohl používat. Aplikace se " 149 | "tedy nemůže připojit k rozhraní API Docker Engine. Zjistěte, jak spustit " 150 | "docker bez práv root v příručkách k dockeru, jinak aplikace nebude " 152 | "fungovat správně. Nebo zkontrolujte cestu soketu k Docker API v Nastavení." 153 | 154 | #: src/Widgets/ScreenError.vala:42 155 | msgid "" 156 | "It looks like Docker is not installed on your system. To find out how to " 157 | "install it, see Docker " 158 | "Manuals. Or check your socket path to Docker API in Settings." 159 | msgstr "" 160 | "Zdá se, že Docker není ve vašem systému nainstalován. Chcete-li zjistit, jak " 161 | "jej nainstalovat, přečtěte si příručky docker. Nebo zkontrolujte cestu soketu k Docker API " 163 | "v Nastavení." 164 | 165 | #: src/Widgets/ScreenError.vala:50 166 | msgid "The app cannot connect to Docker API" 167 | msgstr "Aplikace se nemůže připojit k Docker API" 168 | 169 | #: src/Widgets/Screens/DockerContainer/Log.vala:39 170 | msgid "Autoscroll" 171 | msgstr "Automatické posouvání" 172 | 173 | #: src/Widgets/Screens/DockerContainer/Log.vala:51 174 | msgid "Enable autoscroll to bottom border" 175 | msgstr "Povolit automatické posouvání k dolnímu okraji" 176 | 177 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:19 178 | msgid "Start" 179 | msgstr "Spustit" 180 | 181 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:23 182 | msgid "Stop" 183 | msgstr "Zastavit" 184 | 185 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:25 186 | msgid "Unpause" 187 | msgstr "Zrušit pozastavení" 188 | 189 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:56 190 | msgid "Pause" 191 | msgstr "Pozastavit" 192 | 193 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:59 194 | msgid "Container pause error" 195 | msgstr "Chyba pozastavení kontejneru" 196 | 197 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:74 198 | msgid "Restart" 199 | msgstr "Restartovat" 200 | 201 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:76 202 | msgid "Container restart error" 203 | msgstr "Chyba restartování kontejneru" 204 | 205 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:79 206 | msgid "Restarting container" 207 | msgstr "Restartování kontejneru" 208 | 209 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:94 210 | msgid "Remove" 211 | msgstr "Odstranit" 212 | 213 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:97 214 | msgid "Do you really want to remove container?" 215 | msgstr "Opravdu chcete odstranit kontejner?" 216 | 217 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:98 218 | msgid "Yes, remove" 219 | msgstr "Ano, odstranit" 220 | 221 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:99 222 | msgid "Cancel" 223 | msgstr "Zrušit" 224 | 225 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:105 226 | msgid "Container remove error" 227 | msgstr "Chyba při odstraňování kontejneru" 228 | 229 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:107 230 | msgid "Removing container" 231 | msgstr "Odstraňování kontejneru" 232 | 233 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:127 234 | msgid "Info" 235 | msgstr "Informace" 236 | 237 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:129 238 | msgid "Cannot get information" 239 | msgstr "Nelze získat informace" 240 | 241 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:152 242 | msgid "Container action error" 243 | msgstr "Chyba akce s kontejnerem" 244 | 245 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:157 246 | msgid "Container stop error" 247 | msgstr "Chyba zastavení kontejneru" 248 | 249 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:158 250 | msgid "Stopping container" 251 | msgstr "Zastavování kontejneru" 252 | 253 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:163 254 | msgid "Container start error" 255 | msgstr "Chyba spuštění kontejneru" 256 | 257 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:164 258 | msgid "Starting container" 259 | msgstr "Spouštění kontejneru" 260 | 261 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:169 262 | msgid "Container unpause error" 263 | msgstr "Chyba při zrušení pozastavení kontejneru" 264 | 265 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:174 266 | msgid "Container state is unknown" 267 | msgstr "Stav kontejneru není znám" 268 | 269 | #: src/Widgets/Screens/Main/ContainersGrid.vala:81 270 | msgid "No containers" 271 | msgstr "Žádné kontejnery" 272 | 273 | #: src/Widgets/Screens/Main/ContainersGridFilter.vala:53 274 | msgid "Sort by:" 275 | msgstr "Řadit podle:" 276 | -------------------------------------------------------------------------------- /po/en.po: -------------------------------------------------------------------------------- 1 | # English translations for com.github.sdv43.whaler package. 2 | # Copyright (C) 2022 THE com.github.sdv43.whaler'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the com.github.sdv43.whaler package. 4 | # Automatically generated, 2022. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: com.github.sdv43.whaler\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2022-07-04 08:24+0300\n" 11 | "PO-Revision-Date: 2022-04-09 14:17+0300\n" 12 | "Last-Translator: Automatically generated\n" 13 | "Language-Team: none\n" 14 | "Language: en\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=ASCII\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | 20 | #: data/com.github.sdv43.whaler.desktop.in:5 21 | msgid "Manage your Docker containers" 22 | msgstr "Manage your Docker containers" 23 | 24 | #: data/com.github.sdv43.whaler.desktop.in:11 25 | msgid "Docker;Virtualizaton;Development;" 26 | msgstr "Docker;Virtualizaton;Development;" 27 | 28 | #: data/com.github.sdv43.whaler.appdata.xml.in:5 29 | msgid "Docker Container Management" 30 | msgstr "Docker Container Management" 31 | 32 | #: data/com.github.sdv43.whaler.appdata.xml.in:10 33 | msgid "" 34 | "Whaler provides basic functionality for managing Docker containers. The app " 35 | "can start and stop both standalone containers and docker-compose " 36 | "applications. Also, it supports viewing container logs." 37 | msgstr "" 38 | "Whaler provides basic functionality for managing Docker containers. The app " 39 | "can start and stop both standalone containers and docker-compose " 40 | "applications. Also, it supports viewing container logs." 41 | 42 | #: data/com.github.sdv43.whaler.appdata.xml.in:15 43 | msgid "" 44 | "The solution is perfect for those who are looking for a simple tool to " 45 | "perform some basic actions. For the app to run correctly, make sure that " 46 | "Docker is installed on your system." 47 | msgstr "" 48 | "The solution is perfect for those who are looking for a simple tool to " 49 | "perform some basic actions. For the app to run correctly, make sure that " 50 | "Docker is installed on your system." 51 | 52 | #: src/Utils/Sorting/SortingName.vala:12 53 | #: src/Widgets/Utils/ContainerInfoDialog.vala:52 54 | msgid "Name" 55 | msgstr "Name" 56 | 57 | #: src/Utils/Sorting/SortingType.vala:10 58 | msgid "Type" 59 | msgstr "Type" 60 | 61 | #: src/Widgets/Utils/SettingsDialog.vala:11 62 | #: src/Widgets/Utils/ContainerInfoDialog.vala:13 63 | msgid "Close" 64 | msgstr "Close" 65 | 66 | #: src/Widgets/Utils/SettingsDialog.vala:26 67 | msgid "Settings" 68 | msgstr "Settings" 69 | 70 | #: src/Widgets/Utils/SettingsDialog.vala:41 71 | msgid "API socket path:" 72 | msgstr "API socket path:" 73 | 74 | #: src/Widgets/Utils/SettingsDialog.vala:104 75 | msgid "Check connection" 76 | msgstr "Check connection" 77 | 78 | #: src/Widgets/Utils/SettingsDialog.vala:124 79 | msgid "Success" 80 | msgstr "Success" 81 | 82 | #: src/Widgets/Utils/SettingsDialog.vala:124 83 | msgid "Error" 84 | msgstr "Error" 85 | 86 | #: src/Widgets/Utils/SettingsDialog.vala:141 87 | #, c-format 88 | msgid "Incorrect socket path \"%s\"" 89 | msgstr "Incorrect socket path \"%s\"" 90 | 91 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:11 92 | msgid "Running" 93 | msgstr "Running" 94 | 95 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:16 96 | msgid "Paused" 97 | msgstr "Paused" 98 | 99 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:21 100 | msgid "Stopped" 101 | msgstr "Stopped" 102 | 103 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:26 104 | msgid "Unknown" 105 | msgstr "Unknown" 106 | 107 | #: src/Widgets/Utils/ContainerInfoDialog.vala:53 108 | msgid "Image" 109 | msgstr "Image" 110 | 111 | #: src/Widgets/Utils/ContainerInfoDialog.vala:54 112 | msgid "Status" 113 | msgstr "Status" 114 | 115 | #: src/Widgets/Utils/ContainerInfoDialog.vala:57 116 | msgid "Ports" 117 | msgstr "Ports" 118 | 119 | #: src/Widgets/Utils/ContainerInfoDialog.vala:61 120 | msgid "Binds" 121 | msgstr "Binds" 122 | 123 | #: src/Widgets/Utils/ContainerInfoDialog.vala:65 124 | msgid "Env" 125 | msgstr "Env" 126 | 127 | #: src/Widgets/HeaderBar.vala:15 128 | msgid "Back" 129 | msgstr "Back" 130 | 131 | #: src/Widgets/HeaderBar.vala:40 132 | msgid "Update docker container list" 133 | msgstr "Update docker container list" 134 | 135 | #: src/Widgets/HeaderBar.vala:87 src/Widgets/ScreenError.vala:56 136 | msgid "Open settings" 137 | msgstr "Open settings" 138 | 139 | #: src/Widgets/ScreenError.vala:33 140 | msgid "" 141 | "It looks like Docker requires root rights to use it. Thus, the application " 142 | "cannot connect to Docker Engine API. Find out how to run docker without root " 143 | "rights in Docker Manuals, otherwise the application cannot work correctly. Or " 145 | "check your socket path to Docker API in Settings." 146 | msgstr "" 147 | "It looks like Docker requires root rights to use it. Thus, the application " 148 | "cannot connect to Docker Engine API. Find out how to run docker without root " 149 | "rights in Docker Manuals, otherwise the application cannot work correctly. Or " 151 | "check your socket path to Docker API in Settings." 152 | 153 | #: src/Widgets/ScreenError.vala:42 154 | msgid "" 155 | "It looks like Docker is not installed on your system. To find out how to " 156 | "install it, see Docker " 157 | "Manuals. Or check your socket path to Docker API in Settings." 158 | msgstr "" 159 | "It looks like Docker is not installed on your system. To find out how to " 160 | "install it, see Docker " 161 | "Manuals. Or check your socket path to Docker API in Settings." 162 | 163 | #: src/Widgets/ScreenError.vala:50 164 | msgid "The app cannot connect to Docker API" 165 | msgstr "The app cannot connect to Docker API" 166 | 167 | #: src/Widgets/Screens/DockerContainer/Log.vala:39 168 | msgid "Autoscroll" 169 | msgstr "Autoscroll" 170 | 171 | #: src/Widgets/Screens/DockerContainer/Log.vala:51 172 | msgid "Enable autoscroll to bottom border" 173 | msgstr "Enable autoscroll to bottom border" 174 | 175 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:19 176 | msgid "Start" 177 | msgstr "Start" 178 | 179 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:23 180 | msgid "Stop" 181 | msgstr "Stop" 182 | 183 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:25 184 | msgid "Unpause" 185 | msgstr "Unpause" 186 | 187 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:56 188 | msgid "Pause" 189 | msgstr "Pause" 190 | 191 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:59 192 | msgid "Container pause error" 193 | msgstr "Container pause error" 194 | 195 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:74 196 | msgid "Restart" 197 | msgstr "Restart" 198 | 199 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:76 200 | msgid "Container restart error" 201 | msgstr "Container restart error" 202 | 203 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:79 204 | msgid "Restarting container" 205 | msgstr "Restarting container" 206 | 207 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:94 208 | msgid "Remove" 209 | msgstr "Remove" 210 | 211 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:97 212 | msgid "Do you really want to remove container?" 213 | msgstr "Do you really want to remove container?" 214 | 215 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:98 216 | msgid "Yes, remove" 217 | msgstr "Yes, remove" 218 | 219 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:99 220 | msgid "Cancel" 221 | msgstr "Cancel" 222 | 223 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:105 224 | msgid "Container remove error" 225 | msgstr "Container remove error" 226 | 227 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:107 228 | msgid "Removing container" 229 | msgstr "Removing container" 230 | 231 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:127 232 | msgid "Info" 233 | msgstr "Info" 234 | 235 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:129 236 | msgid "Cannot get information" 237 | msgstr "The app cannot get container data" 238 | 239 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:152 240 | msgid "Container action error" 241 | msgstr "Container action error" 242 | 243 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:157 244 | msgid "Container stop error" 245 | msgstr "Container stop error" 246 | 247 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:158 248 | msgid "Stopping container" 249 | msgstr "Stopping container" 250 | 251 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:163 252 | msgid "Container start error" 253 | msgstr "Container start error" 254 | 255 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:164 256 | msgid "Starting container" 257 | msgstr "Starting container" 258 | 259 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:169 260 | msgid "Container unpause error" 261 | msgstr "Container unpause error" 262 | 263 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:174 264 | msgid "Container state is unknown" 265 | msgstr "Container state is unknown" 266 | 267 | #: src/Widgets/Screens/Main/ContainersGrid.vala:81 268 | msgid "No containers" 269 | msgstr "No containers" 270 | 271 | #: src/Widgets/Screens/Main/ContainersGridFilter.vala:53 272 | msgid "Sort by:" 273 | msgstr "Sort by:" 274 | -------------------------------------------------------------------------------- /po/fr.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the com.github.sdv43.whaler package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: com.github.sdv43.whaler\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2022-07-04 08:24+0300\n" 11 | "PO-Revision-Date: 2022-07-04 00:02+0200\n" 12 | "Last-Translator: Irénée Thirion \n" 13 | "Language-Team: \n" 14 | "Language: fr\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 19 | "X-Generator: Poedit 3.1\n" 20 | 21 | #: data/com.github.sdv43.whaler.desktop.in:5 22 | msgid "Manage your Docker containers" 23 | msgstr "Gérez vos conteneurs Docker" 24 | 25 | #: data/com.github.sdv43.whaler.desktop.in:11 26 | msgid "Docker;Virtualizaton;Development;" 27 | msgstr "Docker;Virtualisation;Développement;" 28 | 29 | #: data/com.github.sdv43.whaler.appdata.xml.in:5 30 | msgid "Docker Container Management" 31 | msgstr "Gestion des conteneurs Docker" 32 | 33 | #: data/com.github.sdv43.whaler.appdata.xml.in:10 34 | msgid "" 35 | "Whaler provides basic functionality for managing Docker containers. The app " 36 | "can start and stop both standalone containers and docker-compose " 37 | "applications. Also, it supports viewing container logs." 38 | msgstr "" 39 | "Whaler fournit les fonctionnalités de base pour la gestion des conteneurs " 40 | "Docker. L'application peut démarrer et arrêter les conteneurs autonomes et " 41 | "les applications composées de Docker. Elle permet également de visualiser " 42 | "les journaux des conteneurs." 43 | 44 | #: data/com.github.sdv43.whaler.appdata.xml.in:15 45 | msgid "" 46 | "The solution is perfect for those who are looking for a simple tool to " 47 | "perform some basic actions. For the app to run correctly, make sure that " 48 | "Docker is installed on your system." 49 | msgstr "" 50 | "La solution est parfaite pour ceux qui recherchent un outil simple pour " 51 | "accomplir des actions basiques. Pour que l'application fonctionne " 52 | "correctement, assurez-vous que Docker est installé sur votre système." 53 | 54 | #: src/Utils/Sorting/SortingName.vala:12 55 | #: src/Widgets/Utils/ContainerInfoDialog.vala:52 56 | msgid "Name" 57 | msgstr "Nom" 58 | 59 | #: src/Utils/Sorting/SortingType.vala:10 60 | msgid "Type" 61 | msgstr "Type" 62 | 63 | #: src/Widgets/Utils/SettingsDialog.vala:11 64 | #: src/Widgets/Utils/ContainerInfoDialog.vala:13 65 | msgid "Close" 66 | msgstr "Fermer" 67 | 68 | #: src/Widgets/Utils/SettingsDialog.vala:26 69 | msgid "Settings" 70 | msgstr "Paramètres" 71 | 72 | #: src/Widgets/Utils/SettingsDialog.vala:41 73 | msgid "API socket path:" 74 | msgstr "Chemin de la prise de l’API :" 75 | 76 | #: src/Widgets/Utils/SettingsDialog.vala:104 77 | msgid "Check connection" 78 | msgstr "Vérifier la connexion" 79 | 80 | #: src/Widgets/Utils/SettingsDialog.vala:124 81 | msgid "Success" 82 | msgstr "Succès" 83 | 84 | #: src/Widgets/Utils/SettingsDialog.vala:124 85 | msgid "Error" 86 | msgstr "Erreur" 87 | 88 | #: src/Widgets/Utils/SettingsDialog.vala:141 89 | #, c-format 90 | msgid "Incorrect socket path \"%s\"" 91 | msgstr "Chemin de prise incorrect « %s »" 92 | 93 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:11 94 | msgid "Running" 95 | msgstr "En cours d’exécution" 96 | 97 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:16 98 | msgid "Paused" 99 | msgstr "En pause" 100 | 101 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:21 102 | msgid "Stopped" 103 | msgstr "Arrêté" 104 | 105 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:26 106 | msgid "Unknown" 107 | msgstr "Inconnu" 108 | 109 | #: src/Widgets/Utils/ContainerInfoDialog.vala:53 110 | msgid "Image" 111 | msgstr "Image" 112 | 113 | #: src/Widgets/Utils/ContainerInfoDialog.vala:54 114 | msgid "Status" 115 | msgstr "État" 116 | 117 | #: src/Widgets/Utils/ContainerInfoDialog.vala:57 118 | msgid "Ports" 119 | msgstr "Connexions" 120 | 121 | #: src/Widgets/Utils/ContainerInfoDialog.vala:61 122 | msgid "Binds" 123 | msgstr "Liens" 124 | 125 | #: src/Widgets/Utils/ContainerInfoDialog.vala:65 126 | msgid "Env" 127 | msgstr "Env" 128 | 129 | #: src/Widgets/HeaderBar.vala:15 130 | msgid "Back" 131 | msgstr "Retour" 132 | 133 | #: src/Widgets/HeaderBar.vala:40 134 | msgid "Update docker container list" 135 | msgstr "Mettre à jour la liste des conteneurs" 136 | 137 | #: src/Widgets/HeaderBar.vala:87 src/Widgets/ScreenError.vala:56 138 | msgid "Open settings" 139 | msgstr "Ouvrir les paramètres" 140 | 141 | #: src/Widgets/ScreenError.vala:33 142 | msgid "" 143 | "It looks like Docker requires root rights to use it. Thus, the application " 144 | "cannot connect to Docker Engine API. Find out how to run docker without root " 145 | "rights in Docker Manuals, otherwise the application cannot work correctly. Or " 147 | "check your socket path to Docker API in Settings." 148 | msgstr "" 149 | "Il semble que Docker requiert des droits root pour l’utiliser. Ainsi, " 150 | "l’application ne peut pas se connecter à l'API du moteur Docker. Découvrez " 151 | "comment exécuter Docker sans droits root dans les Manuels Docker, sinon " 153 | "l’application ne pourra pas fonctionner correctement. Ou vérifiez votre " 154 | "chemin de votre prise vers l’API de Docker dans les paramètres." 155 | 156 | #: src/Widgets/ScreenError.vala:42 157 | msgid "" 158 | "It looks like Docker is not installed on your system. To find out how to " 159 | "install it, see Docker " 160 | "Manuals. Or check your socket path to Docker API in Settings." 161 | msgstr "" 162 | "Il semble que Docker n’est pas installé sur votre système.\n" 163 | "Pour trouver comment l’installer, voir Manuels Docker.\n" 165 | "Ou vérifiez votre chemin de prise vers l’API de Docker dans les paramètres." 166 | 167 | #: src/Widgets/ScreenError.vala:50 168 | msgid "The app cannot connect to Docker API" 169 | msgstr "L’application ne peut se connecter à l’API Docker" 170 | 171 | #: src/Widgets/Screens/DockerContainer/Log.vala:39 172 | msgid "Autoscroll" 173 | msgstr "Défilement automatique" 174 | 175 | #: src/Widgets/Screens/DockerContainer/Log.vala:51 176 | msgid "Enable autoscroll to bottom border" 177 | msgstr "Activer le défilement automatique vers le bord inférieur" 178 | 179 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:19 180 | msgid "Start" 181 | msgstr "Démarrer" 182 | 183 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:23 184 | msgid "Stop" 185 | msgstr "Arrêter" 186 | 187 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:25 188 | msgid "Unpause" 189 | msgstr "Reprendre" 190 | 191 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:56 192 | msgid "Pause" 193 | msgstr "Mettre en Pause" 194 | 195 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:59 196 | msgid "Container pause error" 197 | msgstr "Erreur de pause du conteneur" 198 | 199 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:74 200 | msgid "Restart" 201 | msgstr "Redémarrer" 202 | 203 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:76 204 | msgid "Container restart error" 205 | msgstr "Erreur de redémarrage du conteneur" 206 | 207 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:79 208 | msgid "Restarting container" 209 | msgstr "Redémarrage du conteneur" 210 | 211 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:94 212 | msgid "Remove" 213 | msgstr "Supprimer" 214 | 215 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:97 216 | msgid "Do you really want to remove container?" 217 | msgstr "Voulez-vous vraiment supprimer le conteneur ?" 218 | 219 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:98 220 | msgid "Yes, remove" 221 | msgstr "Oui, supprimer" 222 | 223 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:99 224 | msgid "Cancel" 225 | msgstr "Annuler" 226 | 227 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:105 228 | msgid "Container remove error" 229 | msgstr "Erreur dans la suppression du conteneur" 230 | 231 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:107 232 | msgid "Removing container" 233 | msgstr "Suppression du conteneur" 234 | 235 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:127 236 | msgid "Info" 237 | msgstr "Infos" 238 | 239 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:129 240 | msgid "Cannot get information" 241 | msgstr "Impossible d’obtenir les informations" 242 | 243 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:152 244 | msgid "Container action error" 245 | msgstr "Erreur d’action du conteneur" 246 | 247 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:157 248 | msgid "Container stop error" 249 | msgstr "Erreur d’arrêt du conteneur" 250 | 251 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:158 252 | msgid "Stopping container" 253 | msgstr "Arrêt du conteneur" 254 | 255 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:163 256 | msgid "Container start error" 257 | msgstr "Erreur de démarrage du conteneur" 258 | 259 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:164 260 | msgid "Starting container" 261 | msgstr "Démarrage du conteneur" 262 | 263 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:169 264 | msgid "Container unpause error" 265 | msgstr "Erreur de reprise du conteneur" 266 | 267 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:174 268 | msgid "Container state is unknown" 269 | msgstr "L’état du conteneur est inconnu" 270 | 271 | #: src/Widgets/Screens/Main/ContainersGrid.vala:81 272 | msgid "No containers" 273 | msgstr "Pas de conteneurs" 274 | 275 | #: src/Widgets/Screens/Main/ContainersGridFilter.vala:53 276 | msgid "Sort by:" 277 | msgstr "Trier par :" 278 | -------------------------------------------------------------------------------- /po/it.po: -------------------------------------------------------------------------------- 1 | # ITALIAN TRANSLATION FOR WHALER. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the com.github.sdv43.whaler package. 4 | # ALBANO BATTISTELLA , 2022,2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: com.github.sdv43.whaler\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2022-07-04 08:24+0300\n" 11 | "PO-Revision-Date: 2024-02-06 21:26+0100\n" 12 | "Last-Translator: Albano Battistella \n" 13 | "Language-Team: Italian \n" 14 | "Language: it\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | 20 | #: data/com.github.sdv43.whaler.desktop.in:5 21 | msgid "Manage your Docker containers" 22 | msgstr "Gestisci i tuoi contenitori Docker" 23 | 24 | #: data/com.github.sdv43.whaler.desktop.in:11 25 | msgid "Docker;Virtualizaton;Development;" 26 | msgstr "Docker;Virtualizzazione;Sviluppo;" 27 | 28 | #: data/com.github.sdv43.whaler.appdata.xml.in:5 29 | msgid "Docker Container Management" 30 | msgstr "Gestione dei container Docker" 31 | 32 | #: data/com.github.sdv43.whaler.appdata.xml.in:10 33 | msgid "" 34 | "Whaler provides basic functionality for managing Docker containers. The app " 35 | "can start and stop both standalone containers and docker-compose " 36 | "applications. Also, it supports viewing container logs." 37 | msgstr "" 38 | "Whaler fornisce funzionalità di base per la gestione dei container Docker. " 39 | "L'app può avviare e interrompere sia i contenitori standalone che le " 40 | "applicazioni docker-compose. Inoltre, supporta la visualizzazione dei " 41 | "registri del container." 42 | 43 | #: data/com.github.sdv43.whaler.appdata.xml.in:15 44 | msgid "" 45 | "The solution is perfect for those who are looking for a simple tool to " 46 | "perform some basic actions. For the app to run correctly, make sure that " 47 | "Docker is installed on your system." 48 | msgstr "" 49 | "La soluzione è perfetta per chi cerca un semplice strumento per eseguire " 50 | "alcune azioni di base. Affinché l'app funzioni correttamente, assicurati che " 51 | "Docker e docker-compose siano installati sul tuo sistema." 52 | 53 | #: src/Utils/Sorting/SortingName.vala:12 54 | #: src/Widgets/Utils/ContainerInfoDialog.vala:52 55 | msgid "Name" 56 | msgstr "Nome" 57 | 58 | #: src/Utils/Sorting/SortingType.vala:10 59 | msgid "Type" 60 | msgstr "Tipo" 61 | 62 | #: src/Widgets/Utils/SettingsDialog.vala:11 63 | #: src/Widgets/Utils/ContainerInfoDialog.vala:13 64 | msgid "Close" 65 | msgstr "Chiudi" 66 | 67 | #: src/Widgets/Utils/SettingsDialog.vala:26 68 | msgid "Settings" 69 | msgstr "Impostazioni" 70 | 71 | #: src/Widgets/Utils/SettingsDialog.vala:41 72 | msgid "API socket path:" 73 | msgstr "Percorso del socket API:" 74 | 75 | #: src/Widgets/Utils/SettingsDialog.vala:104 76 | msgid "Check connection" 77 | msgstr "Controlla la connessione" 78 | 79 | #: src/Widgets/Utils/SettingsDialog.vala:124 80 | msgid "Success" 81 | msgstr "Successo" 82 | 83 | #: src/Widgets/Utils/SettingsDialog.vala:124 84 | msgid "Error" 85 | msgstr "Errore" 86 | 87 | #: src/Widgets/Utils/SettingsDialog.vala:141 88 | #, c-format 89 | msgid "Incorrect socket path \"%s\"" 90 | msgstr "Percorso socket errato \"%s\"" 91 | 92 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:11 93 | msgid "Running" 94 | msgstr "In esecuzione" 95 | 96 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:16 97 | msgid "Paused" 98 | msgstr "In pausa" 99 | 100 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:21 101 | msgid "Stopped" 102 | msgstr "Fermato" 103 | 104 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:26 105 | msgid "Unknown" 106 | msgstr "Sconosciuto" 107 | 108 | #: src/Widgets/Utils/ContainerInfoDialog.vala:53 109 | msgid "Image" 110 | msgstr "Immagine" 111 | 112 | #: src/Widgets/Utils/ContainerInfoDialog.vala:54 113 | msgid "Status" 114 | msgstr "Stato" 115 | 116 | #: src/Widgets/Utils/ContainerInfoDialog.vala:57 117 | msgid "Ports" 118 | msgstr "Porte" 119 | 120 | #: src/Widgets/Utils/ContainerInfoDialog.vala:61 121 | msgid "Binds" 122 | msgstr "" 123 | 124 | #: src/Widgets/Utils/ContainerInfoDialog.vala:65 125 | msgid "Env" 126 | msgstr "" 127 | 128 | #: src/Widgets/HeaderBar.vala:15 129 | msgid "Back" 130 | msgstr "Indietro" 131 | 132 | #: src/Widgets/HeaderBar.vala:40 133 | msgid "Update docker container list" 134 | msgstr "Aggiorna elenco docker container" 135 | 136 | #: src/Widgets/HeaderBar.vala:87 src/Widgets/ScreenError.vala:56 137 | msgid "Open settings" 138 | msgstr "Apri le impostazioni" 139 | 140 | #: src/Widgets/ScreenError.vala:33 141 | msgid "" 142 | "It looks like Docker requires root rights to use it. Thus, the application " 143 | "cannot connect to Docker Engine API. Find out how to run docker without root " 144 | "rights in Docker Manuals, otherwise the application cannot work correctly. Or " 146 | "check your socket path to Docker API in Settings." 147 | msgstr "" 148 | "Sembra che Docker richieda i diritti di root per usarlo. Quindi, " 149 | "l'applicazione non potrà connettersi all'API Docker Engine. Scopri come " 150 | "eseguire Docker senza i diritti di root in Manuali Docker, altrimenti " 152 | "l'applicazione non può funzionare correttamente. Oppure controlla il " 153 | "percorso del tuo socket per l'API Docker in Impostazioni." 154 | 155 | #: src/Widgets/ScreenError.vala:42 156 | msgid "" 157 | "It looks like Docker is not installed on your system. To find out how to " 158 | "install it, see Docker " 159 | "Manuals. Or check your socket path to Docker API in Settings." 160 | msgstr "" 161 | "Sembra che Docker non sia installato sul tuo sistema.\n" 162 | "Per scoprire come installarlo, vedere Manuali Docker. Oppure controlla il percorso del tuo " 164 | "socket per l'API Docker in Impostazioni." 165 | 166 | #: src/Widgets/ScreenError.vala:50 167 | msgid "The app cannot connect to Docker API" 168 | msgstr "L'app non può connettersi all'API Docker" 169 | 170 | #: src/Widgets/Screens/DockerContainer/Log.vala:39 171 | msgid "Autoscroll" 172 | msgstr "Scorrimento automatico" 173 | 174 | #: src/Widgets/Screens/DockerContainer/Log.vala:51 175 | msgid "Enable autoscroll to bottom border" 176 | msgstr "Abilita lo scorrimento automatico fino al bordo inferiore" 177 | 178 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:19 179 | msgid "Start" 180 | msgstr "Avvia" 181 | 182 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:23 183 | msgid "Stop" 184 | msgstr "Ferma" 185 | 186 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:25 187 | msgid "Unpause" 188 | msgstr "Riattiva" 189 | 190 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:56 191 | msgid "Pause" 192 | msgstr "Pausa" 193 | 194 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:59 195 | msgid "Container pause error" 196 | msgstr "Errore di pausa del container" 197 | 198 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:74 199 | msgid "Restart" 200 | msgstr "Riavvia" 201 | 202 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:76 203 | msgid "Container restart error" 204 | msgstr "Errore di riavvio del container" 205 | 206 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:79 207 | msgid "Restarting container" 208 | msgstr "Container in riavvio" 209 | 210 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:94 211 | msgid "Remove" 212 | msgstr "Rimuovi" 213 | 214 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:97 215 | msgid "Do you really want to remove container?" 216 | msgstr "Vuoi davvero rimuovere il container" 217 | 218 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:98 219 | msgid "Yes, remove" 220 | msgstr "Sì, rimuovi" 221 | 222 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:99 223 | msgid "Cancel" 224 | msgstr "Cancella" 225 | 226 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:105 227 | msgid "Container remove error" 228 | msgstr "Errore di rimozione del container" 229 | 230 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:107 231 | msgid "Removing container" 232 | msgstr "Rimozione del container" 233 | 234 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:127 235 | msgid "Info" 236 | msgstr "Informazioni" 237 | 238 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:129 239 | msgid "Cannot get information" 240 | msgstr "Impossibile ottenere informazioni" 241 | 242 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:152 243 | msgid "Container action error" 244 | msgstr "Errore di azione del container" 245 | 246 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:157 247 | msgid "Container stop error" 248 | msgstr "Errore di arresto del container" 249 | 250 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:158 251 | msgid "Stopping container" 252 | msgstr "Arresto del container" 253 | 254 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:163 255 | msgid "Container start error" 256 | msgstr "Errore di avvio del container" 257 | 258 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:164 259 | msgid "Starting container" 260 | msgstr "Container in avvio" 261 | 262 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:169 263 | msgid "Container unpause error" 264 | msgstr "Errore di ripristino del container" 265 | 266 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:174 267 | msgid "Container state is unknown" 268 | msgstr "Lo stato del container è sconosciuto" 269 | 270 | #: src/Widgets/Screens/Main/ContainersGrid.vala:81 271 | msgid "No containers" 272 | msgstr "Nessun container" 273 | 274 | #: src/Widgets/Screens/Main/ContainersGridFilter.vala:53 275 | msgid "Sort by:" 276 | msgstr "Ordina per:" 277 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext( 2 | meson.project_name(), 3 | args: '--directory=' + meson.source_root(), 4 | preset: 'glib' 5 | ) -------------------------------------------------------------------------------- /po/ru.po: -------------------------------------------------------------------------------- 1 | # Russian translations for com.github.sdv43.whaler package. 2 | # Copyright (C) 2022 THE com.github.sdv43.whaler'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the com.github.sdv43.whaler package. 4 | # Automatically generated, 2022. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: com.github.sdv43.whaler\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2022-07-04 08:24+0300\n" 11 | "PO-Revision-Date: 2022-03-14 08:44+0300\n" 12 | "Last-Translator: Automatically generated\n" 13 | "Language-Team: none\n" 14 | "Language: ru\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 19 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 20 | 21 | #: data/com.github.sdv43.whaler.desktop.in:5 22 | msgid "Manage your Docker containers" 23 | msgstr "Управляй Docker контейнерами" 24 | 25 | #: data/com.github.sdv43.whaler.desktop.in:11 26 | msgid "Docker;Virtualizaton;Development;" 27 | msgstr "Docker;Virtualizaton;Development;" 28 | 29 | #: data/com.github.sdv43.whaler.appdata.xml.in:5 30 | msgid "Docker Container Management" 31 | msgstr "Управление Docker контейнерами" 32 | 33 | #: data/com.github.sdv43.whaler.appdata.xml.in:10 34 | msgid "" 35 | "Whaler provides basic functionality for managing Docker containers. The app " 36 | "can start and stop both standalone containers and docker-compose " 37 | "applications. Also, it supports viewing container logs." 38 | msgstr "" 39 | "Whaler предоставляет базовый функционал для работы с Docker контейнерами. C " 40 | "его помощью Вы сможете запускать и останаваливать как отдельные контейнеры, " 41 | "так и docker-compose приложения. Также есть возможность просматривать логи " 42 | "контейнера." 43 | 44 | #: data/com.github.sdv43.whaler.appdata.xml.in:15 45 | msgid "" 46 | "The solution is perfect for those who are looking for a simple tool to " 47 | "perform some basic actions. For the app to run correctly, make sure that " 48 | "Docker is installed on your system." 49 | msgstr "" 50 | "Это решение отлично подойдет тем кому нужен простой инструмент для базовых " 51 | "действий. Для корректной работы приложения, Docker должен быть установлен в " 52 | "системе." 53 | 54 | #: src/Utils/Sorting/SortingName.vala:12 55 | #: src/Widgets/Utils/ContainerInfoDialog.vala:52 56 | msgid "Name" 57 | msgstr "Название" 58 | 59 | #: src/Utils/Sorting/SortingType.vala:10 60 | msgid "Type" 61 | msgstr "Тип" 62 | 63 | #: src/Widgets/Utils/SettingsDialog.vala:11 64 | #: src/Widgets/Utils/ContainerInfoDialog.vala:13 65 | msgid "Close" 66 | msgstr "Закрыть" 67 | 68 | #: src/Widgets/Utils/SettingsDialog.vala:26 69 | msgid "Settings" 70 | msgstr "Настройки" 71 | 72 | #: src/Widgets/Utils/SettingsDialog.vala:41 73 | msgid "API socket path:" 74 | msgstr "Путь к API сокету:" 75 | 76 | #: src/Widgets/Utils/SettingsDialog.vala:104 77 | msgid "Check connection" 78 | msgstr "Проверить соединение" 79 | 80 | #: src/Widgets/Utils/SettingsDialog.vala:124 81 | msgid "Success" 82 | msgstr "Успешно" 83 | 84 | #: src/Widgets/Utils/SettingsDialog.vala:124 85 | msgid "Error" 86 | msgstr "Ошибка" 87 | 88 | #: src/Widgets/Utils/SettingsDialog.vala:141 89 | #, c-format 90 | msgid "Incorrect socket path \"%s\"" 91 | msgstr "Некорректный путь к сокету \"%s\"" 92 | 93 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:11 94 | msgid "Running" 95 | msgstr "Запущен" 96 | 97 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:16 98 | msgid "Paused" 99 | msgstr "Приостановлен" 100 | 101 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:21 102 | msgid "Stopped" 103 | msgstr "Остановлен" 104 | 105 | #: src/Widgets/Utils/DockerContainerStatusLabel.vala:26 106 | msgid "Unknown" 107 | msgstr "Неизвестно" 108 | 109 | #: src/Widgets/Utils/ContainerInfoDialog.vala:53 110 | msgid "Image" 111 | msgstr "Образ" 112 | 113 | #: src/Widgets/Utils/ContainerInfoDialog.vala:54 114 | msgid "Status" 115 | msgstr "Статус" 116 | 117 | #: src/Widgets/Utils/ContainerInfoDialog.vala:57 118 | msgid "Ports" 119 | msgstr "Порты" 120 | 121 | #: src/Widgets/Utils/ContainerInfoDialog.vala:61 122 | msgid "Binds" 123 | msgstr "Пирвязки" 124 | 125 | #: src/Widgets/Utils/ContainerInfoDialog.vala:65 126 | msgid "Env" 127 | msgstr "Окружение" 128 | 129 | #: src/Widgets/HeaderBar.vala:15 130 | msgid "Back" 131 | msgstr "Назад" 132 | 133 | #: src/Widgets/HeaderBar.vala:40 134 | msgid "Update docker container list" 135 | msgstr "Обновить список docker контейнеров" 136 | 137 | #: src/Widgets/HeaderBar.vala:87 src/Widgets/ScreenError.vala:56 138 | msgid "Open settings" 139 | msgstr "Открыть настройки" 140 | 141 | #: src/Widgets/ScreenError.vala:33 142 | msgid "" 143 | "It looks like Docker requires root rights to use it. Thus, the application " 144 | "cannot connect to Docker Engine API. Find out how to run docker without root " 145 | "rights in Docker Manuals, otherwise the application cannot work correctly. Or " 147 | "check your socket path to Docker API in Settings." 148 | msgstr "" 149 | "Похоже, что Docker требует root права для работы. Из-за этого приложение не " 150 | "может подключиться к Docker Engine API. Инструкция по запуску без root прав " 151 | "доступна в Docker Manuals, иначе приложение не сможет работать " 153 | "корректно. Либо проверьте путь к сокету Docker API в настройках." 154 | 155 | #: src/Widgets/ScreenError.vala:42 156 | msgid "" 157 | "It looks like Docker is not installed on your system. To find out how to " 158 | "install it, see Docker " 159 | "Manuals. Or check your socket path to Docker API in Settings." 160 | msgstr "" 161 | "Похоже, что Docker не установлен в вашей системе.\n" 162 | "Инструкция по установке доступна в Docker Manuals. Либо проверьте путь к сокету Docker API в " 164 | "настройках." 165 | 166 | #: src/Widgets/ScreenError.vala:50 167 | msgid "The app cannot connect to Docker API" 168 | msgstr "Приложение не может подключиться к Docker API" 169 | 170 | #: src/Widgets/Screens/DockerContainer/Log.vala:39 171 | msgid "Autoscroll" 172 | msgstr "Автоскролл" 173 | 174 | #: src/Widgets/Screens/DockerContainer/Log.vala:51 175 | msgid "Enable autoscroll to bottom border" 176 | msgstr "Включить автоскролл к нижней границе" 177 | 178 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:19 179 | msgid "Start" 180 | msgstr "Запустить" 181 | 182 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:23 183 | msgid "Stop" 184 | msgstr "Остановить" 185 | 186 | #: src/Widgets/Screens/DockerContainer/TopBarActions.vala:25 187 | msgid "Unpause" 188 | msgstr "Запустить" 189 | 190 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:56 191 | msgid "Pause" 192 | msgstr "Пауза" 193 | 194 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:59 195 | msgid "Container pause error" 196 | msgstr "Ошибка остановки контейнера" 197 | 198 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:74 199 | msgid "Restart" 200 | msgstr "Перезапустить" 201 | 202 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:76 203 | msgid "Container restart error" 204 | msgstr "Ошибка перезапуска контейнера" 205 | 206 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:79 207 | msgid "Restarting container" 208 | msgstr "Идет перезапуск контейнера" 209 | 210 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:94 211 | msgid "Remove" 212 | msgstr "Удалить" 213 | 214 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:97 215 | msgid "Do you really want to remove container?" 216 | msgstr "Вы действительно хотите удалить контейнер?" 217 | 218 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:98 219 | msgid "Yes, remove" 220 | msgstr "Да, удалить" 221 | 222 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:99 223 | msgid "Cancel" 224 | msgstr "Отмена" 225 | 226 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:105 227 | msgid "Container remove error" 228 | msgstr "Ошибка удаления контейнера" 229 | 230 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:107 231 | msgid "Removing container" 232 | msgstr "Идет удаление контейнера" 233 | 234 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:127 235 | msgid "Info" 236 | msgstr "Информация" 237 | 238 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:129 239 | msgid "Cannot get information" 240 | msgstr "Приложение не может получить информацию о контейнере" 241 | 242 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:152 243 | msgid "Container action error" 244 | msgstr "Ошибка при работе с контейнером" 245 | 246 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:157 247 | msgid "Container stop error" 248 | msgstr "Ошибка остановки контейнера" 249 | 250 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:158 251 | msgid "Stopping container" 252 | msgstr "Идет остановка контейнера" 253 | 254 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:163 255 | msgid "Container start error" 256 | msgstr "Ошибка запуска контейнера" 257 | 258 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:164 259 | msgid "Starting container" 260 | msgstr "Идет запуск контейнера" 261 | 262 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:169 263 | msgid "Container unpause error" 264 | msgstr "Ошибка запуска контейнера" 265 | 266 | #: src/Widgets/Screens/Main/ContainerCardActions.vala:174 267 | msgid "Container state is unknown" 268 | msgstr "Неизвестное состояние контейнера" 269 | 270 | #: src/Widgets/Screens/Main/ContainersGrid.vala:81 271 | msgid "No containers" 272 | msgstr "Список контейнеров пуст" 273 | 274 | #: src/Widgets/Screens/Main/ContainersGridFilter.vala:53 275 | msgid "Sort by:" 276 | msgstr "Сортировать по:" 277 | -------------------------------------------------------------------------------- /src/Application.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Widgets; 13 | using Utils.Sorting; 14 | using Utils.Constants; 15 | 16 | public class Whaler : Gtk.Application { 17 | private static Whaler? instance; 18 | 19 | private Whaler () { 20 | this.application_id = APP_ID; 21 | this.flags |= ApplicationFlags.FLAGS_NONE; 22 | } 23 | 24 | public static Whaler get_instance () { 25 | if (Whaler.instance == null) { 26 | Whaler.instance = new Whaler (); 27 | } 28 | 29 | return Whaler.instance; 30 | } 31 | 32 | public static int main (string[] args) { 33 | return Whaler.get_instance ().run (args); 34 | } 35 | 36 | protected override void activate () { 37 | var window = new Gtk.ApplicationWindow (this) { 38 | default_height = 800, 39 | default_width = 1200, 40 | title = APP_NAME 41 | }; 42 | 43 | Intl.bindtextdomain (APP_ID, LOCALE_DIR); 44 | 45 | window.set_titlebar (new HeaderBar ()); 46 | window.add (ScreenManager.get_instance ()); 47 | 48 | this.styles (window); 49 | this.window_state (window); 50 | 51 | window.show_all (); 52 | 53 | State.Root.get_instance ().init.begin (); 54 | } 55 | 56 | private void window_state (Gtk.Window window) { 57 | var settings = new Settings (APP_ID); 58 | int window_x, window_y, window_width, window_height; 59 | 60 | settings.get ("window-position", "(ii)", out window_x, out window_y); 61 | settings.get ("window-size", "(ii)", out window_width, out window_height); 62 | 63 | window.set_default_size (window_width, window_height); 64 | 65 | if (window_x != -1 || window_y != -1) { 66 | window.move (window_x, window_y); 67 | } else { 68 | window.set_position (Gtk.WindowPosition.CENTER); 69 | } 70 | 71 | if (settings.get_boolean ("window-is-maximized")) { 72 | window.maximize (); 73 | } 74 | 75 | window.delete_event.connect (event => { 76 | settings.set_boolean ("window-is-maximized", window.is_maximized); 77 | 78 | window.get_position (out window_x, out window_y); 79 | window.get_size (out window_width, out window_height); 80 | 81 | settings.set ("window-position", "(ii)", window_x, window_y); 82 | settings.set ("window-size", "(ii)", window_width, window_height); 83 | 84 | return false; 85 | }); 86 | } 87 | 88 | private void styles (Gtk.Window window) { 89 | var settings_granite = Granite.Settings.get_default (); 90 | var theme = new Utils.Theme (); 91 | var icon_theme = Gtk.IconTheme.get_default (); 92 | 93 | theme.apply_styles (window.screen); 94 | icon_theme.add_resource_path (@"$RESOURCE_BASE/icons"); 95 | 96 | settings_granite.notify["prefers-color-scheme"].connect (() => { 97 | theme.apply_styles (window.screen); 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Docker/ContainerLogWatcher.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | using Utils.Constants; 14 | 15 | namespace Docker { 16 | errordomain FrameReaderError { 17 | ERROR 18 | } 19 | 20 | class FrameReader : Object { 21 | public Frame? current_frame; 22 | public DataInputStream data; 23 | public MemoryInputStream memory; 24 | public Container container; 25 | public bool is_reading; 26 | public bool is_tty; 27 | public string since; 28 | public int prev_unparsed_data_size; 29 | public Cancellable reading_cancel; 30 | 31 | public signal void new_line (string line); 32 | 33 | public FrameReader (Container container) { 34 | this.since = "0"; 35 | this.is_reading = false; 36 | this.is_tty = false; 37 | this.current_frame = null; 38 | this.prev_unparsed_data_size = 0; 39 | this.container = container; 40 | this.reading_cancel = new Cancellable (); 41 | this.memory = new MemoryInputStream (); 42 | this.data = new DataInputStream (this.memory); 43 | } 44 | 45 | public static size_t read_body_data (void* buf, size_t size, size_t nmemb, void* data) { 46 | unowned var reader = (FrameReader)data; 47 | 48 | return reader.add_memory_stream_data (buf, size * nmemb); 49 | } 50 | 51 | public async void read () throws FrameReaderError { 52 | try { 53 | if (this.is_reading) { 54 | return; 55 | } 56 | 57 | this.is_reading = true; 58 | yield this.curl_request (); 59 | this.is_reading = false; 60 | } catch (HttpClientError error) { 61 | throw new FrameReaderError.ERROR (@"Http request error: $(error.message)"); 62 | } 63 | } 64 | 65 | public size_t add_memory_stream_data (void* buf, size_t size) { 66 | var settings = new Settings (APP_ID); 67 | var buffer = new uint8[size]; 68 | 69 | Posix.memcpy ((void*)buffer, buf, size); 70 | this.memory.add_data (buffer); 71 | 72 | var is_podman = settings.get_string ("docker-api-socket-path").contains ("podman.sock"); 73 | 74 | if (is_podman || this.is_tty) { 75 | this.parse_podman_frame ((int)size); 76 | } else { 77 | this.parse_frame ((int)size); 78 | } 79 | 80 | return size; 81 | } 82 | 83 | public void parse_frame (int size) { 84 | if (!this.is_reading) { 85 | return; 86 | } 87 | 88 | // For the correct operation of the algorithm, at least 8 bytes of data are needed. 89 | var data_size = this.prev_unparsed_data_size + size; 90 | 91 | // We do not read data until there are 10 bytes 92 | if (data_size < 10) { 93 | this.prev_unparsed_data_size = data_size; 94 | 95 | return; 96 | } else { 97 | this.prev_unparsed_data_size = 0; 98 | } 99 | 100 | try { 101 | while (true) { 102 | var bytes_read = 0; 103 | 104 | // Creating a new frame 105 | if (this.current_frame == null) { 106 | this.current_frame = new Frame (this.data); 107 | 108 | bytes_read += this.current_frame.read_type (); 109 | bytes_read += this.current_frame.read_size (); 110 | 111 | // debug ("create a new frame t:%d, s:%d", this.current_frame.type, this.current_frame.size); 112 | } 113 | 114 | // reading frame body 115 | bytes_read += this.current_frame.read_body (); 116 | 117 | // debug ("bytes read: %d", bytes_read); 118 | 119 | if (this.current_frame.is_finish_reading ()) { 120 | // all frame data has been read 121 | this.new_line ((string)this.current_frame.data.data); 122 | this.since = (new DateTime.now_local ()).to_unix ().to_string (); 123 | this.current_frame = null; 124 | 125 | if (bytes_read == data_size) { 126 | // no data in buffer 127 | break; 128 | } else { 129 | // there is still data in the buffer 130 | data_size -= bytes_read; 131 | continue; 132 | } 133 | } else { 134 | if (bytes_read == data_size) { 135 | // frame has not been read and no data left 136 | break; 137 | } else { 138 | // error: frame has not been read but data is available 139 | warning ("Frame has not been read but data is available"); 140 | } 141 | } 142 | } 143 | } catch (IOError error) { 144 | warning (@"Frame parsing error: $(error.message)"); 145 | } 146 | } 147 | 148 | public void parse_podman_frame (int size) { 149 | try { 150 | uint8[] buff = new uint8[size]; 151 | var bytes_read = (int)this.data.read (buff); 152 | 153 | if (bytes_read != size) { 154 | warning ("Frame has not been read but data is available"); 155 | } 156 | 157 | this.new_line ((string)buff); 158 | } catch (IOError error) { 159 | warning (@"Frame parsing error: $(error.message)"); 160 | } 161 | } 162 | 163 | private async void curl_request () throws HttpClientError { 164 | var settings = new Settings (APP_ID); 165 | var curl = new Curl.EasyHandle (); 166 | var tail = this.since == "0" ? "1000" : "all"; 167 | 168 | Curl.Code r; 169 | 170 | r = curl.setopt (Curl.Option.VERBOSE, false); 171 | assert_true (r == Curl.Code.OK); 172 | r = curl.setopt (Curl.Option.URL, @"http://localhost/v$(DOCKER_ENIGINE_API_VERSION)/containers/$(this.container.id)/logs?stdout=true&stderr=true&follow=true&since=$(this.since)&tail=$(tail)"); 173 | assert_true (r == Curl.Code.OK); 174 | r = curl.setopt (Curl.Option.UNIX_SOCKET_PATH, settings.get_string ("docker-api-socket-path")); 175 | assert_true (r == Curl.Code.OK); 176 | r = curl.setopt (Curl.Option.CUSTOMREQUEST, "GET"); 177 | assert_true (r == Curl.Code.OK); 178 | r = curl.setopt (Curl.Option.WRITEDATA, (void*)this); 179 | assert_true (r == Curl.Code.OK); 180 | r = curl.setopt (Curl.Option.WRITEFUNCTION, FrameReader.read_body_data); 181 | assert_true (r == Curl.Code.OK); 182 | 183 | r = yield this.curl_perform (curl); 184 | 185 | if (r != Curl.Code.OK) { 186 | throw new HttpClientError.ERROR (Curl.Global.strerror (r)); 187 | } 188 | 189 | var http_code = 0; 190 | 191 | curl.getinfo (Curl.Info.RESPONSE_CODE, &http_code); 192 | 193 | // debug ("curl request http code: %d", http_code); 194 | } 195 | 196 | private async Curl.Code curl_perform (Curl.EasyHandle curl) throws HttpClientError { 197 | string? err_msg = null; 198 | var r = Curl.Code.OK; 199 | 200 | var task = new Task (this, this.reading_cancel, (obj, cl_task) => { 201 | try { 202 | r = (Curl.Code)cl_task.propagate_int (); 203 | } catch (Error error) { 204 | err_msg = error.message; 205 | } finally { 206 | this.curl_perform.callback (); 207 | } 208 | }); 209 | 210 | task.set_return_on_cancel (true); 211 | task.set_task_data (curl, null); 212 | task.run_in_thread ((task, http_client, curl, cancellable) => { 213 | unowned var cl_curl = (Curl.EasyHandle)curl; 214 | 215 | var cl_r = cl_curl.perform (); 216 | task.return_int (cl_r); 217 | }); 218 | 219 | yield; 220 | 221 | if (err_msg != null) { 222 | throw new HttpClientError.ERROR (@"Curl perform error: $err_msg"); 223 | } 224 | 225 | return r; 226 | } 227 | } 228 | 229 | class Frame : Object { 230 | public int type; 231 | public int size; 232 | public Array data; 233 | public DataInputStream stream; 234 | 235 | public Frame (DataInputStream stream) { 236 | this.type = -1; 237 | this.size = 0; 238 | this.data = new Array(); 239 | this.stream = stream; 240 | } 241 | 242 | public int read_type () throws IOError { 243 | // stream type https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach 244 | this.stream.byte_order = DataStreamByteOrder.LITTLE_ENDIAN; 245 | this.type = (int)this.stream.read_uint32 (); 246 | this.stream.byte_order = DataStreamByteOrder.BIG_ENDIAN; 247 | 248 | return 4; 249 | } 250 | 251 | public int read_size () throws IOError { 252 | this.size = (int)this.stream.read_uint32 (); 253 | 254 | return 4; 255 | } 256 | 257 | public int read_body () throws IOError { 258 | uint8[] buff = new uint8[this.size - this.data.length]; 259 | 260 | var bytes_read_total = 0; 261 | var bytes_read = 0; 262 | 263 | while ((bytes_read = (int)this.stream.read (buff)) != 0) { 264 | this.data.append_vals (buff, bytes_read); 265 | bytes_read_total += bytes_read; 266 | 267 | if (this.data.length == this.size) { 268 | break; 269 | } 270 | } 271 | 272 | return bytes_read_total; 273 | } 274 | 275 | public bool is_finish_reading () { 276 | return this.size == this.data.length; 277 | } 278 | } 279 | 280 | class ContainerLogWatcher : Object { 281 | public Gee.ArrayList containers {get; construct set;} 282 | public Gtk.TextBuffer text_buffer {get; construct set;} 283 | public Gee.HashMap readers; 284 | private bool is_label_visible; 285 | 286 | public ContainerLogWatcher (DockerContainer container, Gtk.TextBuffer buffer) { 287 | this.is_label_visible = container.type == DockerContainerType.GROUP; 288 | this.text_buffer = buffer; 289 | this.containers = new Gee.ArrayList (DockerContainer.equal); 290 | this.readers = new Gee.HashMap( 291 | null, 292 | DockerContainer.equal, 293 | (a, b) => {return a.container.id == b.container.id;} 294 | ); 295 | 296 | if (container.type == DockerContainerType.GROUP) { 297 | this.containers.add_all (container.services); 298 | } else { 299 | this.containers.add (container); 300 | } 301 | } 302 | 303 | public void watching_start () { 304 | foreach (var container in this.containers) { 305 | var reader = this.readers[container]; 306 | 307 | if (reader == null) { 308 | reader = this.create_reader (container); 309 | this.readers[container] = reader; 310 | } 311 | 312 | reader.read.begin ((_, res) => { 313 | try { 314 | reader.read.end (res); 315 | } catch (FrameReaderError error) { 316 | warning (@"Log reading error: $(error.message)"); 317 | } 318 | }); 319 | } 320 | } 321 | 322 | public void watching_stop () { 323 | foreach (var container in this.containers) { 324 | this.readers[container].reading_cancel.cancel (); 325 | } 326 | } 327 | 328 | private FrameReader create_reader (DockerContainer container) { 329 | assert_true (container.api_container != null); 330 | 331 | var reader = new FrameReader (container.api_container); 332 | reader.is_tty = container.is_tty; 333 | 334 | var label = is_label_visible ? @"$(container.name): " : ""; 335 | 336 | reader.new_line.connect ((line) => { 337 | this.buffer_append (label, line, line.length); 338 | }); 339 | 340 | return reader; 341 | } 342 | 343 | private void buffer_append (string label, string str, int length) { 344 | Idle.add (() => { 345 | text_buffer.insert_at_cursor (label, label.length); 346 | text_buffer.insert_at_cursor (str, length); 347 | 348 | return false; 349 | }, Priority.DEFAULT_IDLE); 350 | } 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/State/Root.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | using Utils.Constants; 14 | using Widgets; 15 | using Docker; 16 | 17 | class State.Root : Object { 18 | private ApiClient api_client; 19 | private static Root? instance; 20 | private string? previuos_screen; 21 | 22 | public bool button_back_visible {get; set;} 23 | public string active_screen {get; set;} 24 | public Gee.ArrayList containers {get; set;} 25 | 26 | public ScreenMain screen_main {get; private set;} 27 | public ScreenDockerContainer screen_docker_container {get; private set;} 28 | 29 | private Root () { 30 | var settings = new Settings (APP_ID); 31 | 32 | this.api_client = new ApiClient (); 33 | this.button_back_visible = false; 34 | this.active_screen = Widgets.ScreenMain.CODE; 35 | this.containers = new Gee.ArrayList (DockerContainer.equal); 36 | this.screen_main = new ScreenMain (this); 37 | this.screen_docker_container = new ScreenDockerContainer (this); 38 | 39 | settings.bind ("docker-api-socket-path", this.api_client.http_client, "unix_socket_path", SettingsBindFlags.GET); 40 | } 41 | 42 | public static Root get_instance () { 43 | if (Root.instance == null) { 44 | Root.instance = new Root (); 45 | } 46 | 47 | return Root.instance; 48 | } 49 | 50 | public async void init () { 51 | try { 52 | yield this.containers_load(); 53 | } catch (ApiClientError error) { 54 | var error_widget = ScreenError.build_error_docker_not_avialable ( 55 | error is ApiClientError.ERROR_NO_ENTRY 56 | ); 57 | 58 | ScreenManager.screen_error_show_widget (error_widget); 59 | } 60 | } 61 | 62 | public void prev_screen () { 63 | if (previuos_screen == null) { 64 | return; 65 | } 66 | 67 | this.active_screen = this.previuos_screen; 68 | this.previuos_screen = null; 69 | this.button_back_visible = false; 70 | } 71 | 72 | public void next_screen (string code) { 73 | this.previuos_screen = this.active_screen; 74 | this.active_screen = code; 75 | this.button_back_visible = true; 76 | } 77 | 78 | public async void containers_load () throws ApiClientError { 79 | this.containers.clear (); 80 | 81 | // grouping containers into applications 82 | var api_containers = yield this.api_client.list_containers (); 83 | string[] projects = {}; 84 | 85 | for (var i = 0; i < api_containers.length; i++) { 86 | var container = api_containers[i]; 87 | 88 | if (container.label_project == null) { 89 | // single container 90 | var docker_container = new DockerContainer.from_docker_api_container (container); 91 | var info = yield this.container_inspect (docker_container); 92 | 93 | docker_container.is_tty = info[docker_container].is_tty; 94 | this.containers.add (docker_container); 95 | } else { 96 | // if the container has already been processed 97 | if (container.label_project in projects) { 98 | continue; 99 | } 100 | 101 | // create group 102 | var container_group = new DockerContainer (); 103 | 104 | var full_config_path = Path.build_filename ( 105 | container.label_workdir, 106 | Path.get_basename (container.label_config) 107 | ); 108 | 109 | container_group.id = full_config_path; 110 | container_group.name = container_group.format_name (container.label_project); 111 | container_group.image = ""; 112 | container_group.type = DockerContainerType.GROUP; 113 | container_group.state = DockerContainerState.UNKNOWN; 114 | container_group.config_path = full_config_path; 115 | container_group.services = new Gee.ArrayList (DockerContainer.equal); 116 | 117 | // search for containers with the same project 118 | var is_all_running = true; 119 | var is_all_paused = true; 120 | var is_all_stopped = true; 121 | 122 | for (var j = i; j < api_containers.length; j++) { 123 | var service = api_containers[j]; 124 | 125 | if (service.label_project != null && service.label_project == container.label_project) { 126 | var s = new DockerContainer.from_docker_api_container (service); 127 | s.name = s.format_name (service.label_service); 128 | 129 | is_all_running = is_all_running && s.state == DockerContainerState.RUNNING; 130 | is_all_paused = is_all_paused && s.state == DockerContainerState.PAUSED; 131 | is_all_stopped = is_all_stopped && s.state == DockerContainerState.STOPPED; 132 | 133 | var info = yield this.container_inspect (s); 134 | s.is_tty = info[s].is_tty; 135 | 136 | container_group.services.add (s); 137 | } 138 | } 139 | 140 | // image 141 | string?[] services = {}; 142 | 143 | foreach (var service in container_group.services) { 144 | services += service.name; 145 | } 146 | 147 | // state 148 | if (is_all_running) { 149 | container_group.state = DockerContainerState.RUNNING; 150 | } 151 | if (is_all_paused) { 152 | container_group.state = DockerContainerState.PAUSED; 153 | } 154 | if (is_all_stopped) { 155 | container_group.state = DockerContainerState.STOPPED; 156 | } 157 | 158 | container_group.image = string.joinv (", ", services); 159 | 160 | // mark that the application has already been processed 161 | projects += container.label_project; 162 | 163 | // saving the container to the resulting array 164 | this.containers.add (container_group); 165 | } 166 | } 167 | 168 | this.notify_property ("containers"); 169 | } 170 | 171 | public async void container_start (DockerContainer container) throws ApiClientError { 172 | if (container.type == DockerContainerType.GROUP) { 173 | foreach (var service in container.services) { 174 | yield this.api_client.start_container (service.api_container); 175 | } 176 | } else { 177 | assert_true (container.api_container != null); 178 | yield this.api_client.start_container (container.api_container); 179 | } 180 | 181 | yield this.containers_load (); 182 | } 183 | 184 | public async void container_stop (DockerContainer container) throws ApiClientError { 185 | if (container.type == DockerContainerType.GROUP) { 186 | foreach (var service in container.services) { 187 | yield this.api_client.stop_container (service.api_container); 188 | } 189 | } else { 190 | assert_true (container.api_container != null); 191 | yield this.api_client.stop_container (container.api_container); 192 | } 193 | 194 | yield this.containers_load (); 195 | } 196 | 197 | public async void container_restart (DockerContainer container) throws ApiClientError { 198 | if (container.type == DockerContainerType.GROUP) { 199 | foreach (var service in container.services) { 200 | yield this.api_client.restart_container (service.api_container); 201 | } 202 | } else { 203 | assert_true (container.api_container != null); 204 | yield this.api_client.restart_container (container.api_container); 205 | } 206 | 207 | yield this.containers_load (); 208 | } 209 | 210 | public async void container_pause (DockerContainer container) throws ApiClientError { 211 | if (container.type == DockerContainerType.GROUP) { 212 | foreach (var service in container.services) { 213 | yield this.api_client.pause_container (service.api_container); 214 | } 215 | } else { 216 | assert_true (container.api_container != null); 217 | yield this.api_client.pause_container (container.api_container); 218 | } 219 | 220 | yield this.containers_load (); 221 | } 222 | 223 | public async void container_unpause (DockerContainer container) throws ApiClientError { 224 | if (container.type == DockerContainerType.GROUP) { 225 | foreach (var service in container.services) { 226 | yield this.api_client.unpause_container (service.api_container); 227 | } 228 | } else { 229 | assert_true (container.api_container != null); 230 | yield this.api_client.unpause_container (container.api_container); 231 | } 232 | 233 | yield this.containers_load (); 234 | } 235 | 236 | public async void container_remove (DockerContainer container) throws ApiClientError { 237 | if (container.type == DockerContainerType.GROUP) { 238 | foreach (var service in container.services) { 239 | yield this.api_client.remove_container (service.api_container); 240 | } 241 | } else { 242 | assert_true (container.api_container != null); 243 | yield this.api_client.remove_container (container.api_container); 244 | } 245 | 246 | yield this.containers_load (); 247 | } 248 | 249 | public async Gee.HashMap container_inspect (DockerContainer container) throws ApiClientError { 250 | var container_info = new Gee.HashMap (); 251 | 252 | if (container.type == DockerContainerType.GROUP) { 253 | foreach (var service in container.services) { 254 | container_info[service] = yield this.api_client.inspect_container (service.api_container); 255 | } 256 | } else { 257 | assert_true (container.api_container != null); 258 | container_info[container] = yield this.api_client.inspect_container (container.api_container); 259 | } 260 | 261 | return container_info; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/State/ScreenDockerContainer.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | 14 | class State.ScreenDockerContainer : Object { 15 | private Root root; 16 | 17 | public bool is_auto_scroll_enable; 18 | public DockerContainer? container {get; set;} 19 | public DockerContainer? service {get; set;} 20 | 21 | public ScreenDockerContainer (Root root) { 22 | this.root = root; 23 | this.is_auto_scroll_enable = false; 24 | this.container = null; 25 | this.service = null; 26 | 27 | this.root.notify["containers"].connect (() => { 28 | if (this.container == null) { 29 | return; 30 | } 31 | 32 | var index = root.containers.index_of (this.container); 33 | 34 | if (index != -1) { 35 | this.container = root.containers[index]; 36 | } 37 | }); 38 | 39 | this.notify["container"].connect (() => { 40 | if (this.container.type == DockerContainerType.GROUP) { 41 | if (this.service == null) { 42 | this.service = this.container; 43 | } else { 44 | var index = this.container.services.index_of (this.service); 45 | 46 | if (index != -1) { 47 | this.service = this.container.services[index]; 48 | } else { 49 | this.service = this.container; 50 | } 51 | } 52 | } else { 53 | this.service = this.container; 54 | } 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/State/ScreenMain.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | using Utils.Sorting; 14 | 15 | class State.ScreenMain : Object { 16 | public string search_term {get; set;} 17 | public SortingInterface? sorting {get; set;} 18 | public Gee.ArrayList containers_prepared {get; private set;} 19 | 20 | public ScreenMain (Root root) { 21 | this.search_term = ""; 22 | this.sorting = new SortingName (); 23 | this.containers_prepared = new Gee.ArrayList (DockerContainer.equal); 24 | 25 | root.notify["containers"].connect (() => { 26 | this.containers_prepared.clear (); 27 | this.containers_prepared.add_all_iterator (root.containers.filter ((container) => { 28 | return container.name.down (container.name.length).index_of (this.search_term, 0) > -1; 29 | })); 30 | this.containers_prepared.sort (this.sorting.compare); 31 | 32 | this.notify_property ("containers-prepared"); 33 | }); 34 | 35 | this.notify["search-term"].connect (() => { 36 | this.containers_prepared.clear (); 37 | this.containers_prepared.add_all_iterator (root.containers.filter ((container) => { 38 | return container.name.down (container.name.length).index_of (this.search_term, 0) > -1; 39 | })); 40 | this.containers_prepared.sort (this.sorting.compare); 41 | 42 | this.notify_property ("containers-prepared"); 43 | }); 44 | 45 | this.notify["sorting"].connect (() => { 46 | this.containers_prepared.sort (this.sorting.compare); 47 | 48 | this.notify_property ("containers-prepared"); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Utils/Constants.vala.in: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | namespace Utils.Constants { 13 | const string APP_NAME = "Whaler"; 14 | const string APP_ID = @APP_ID@; 15 | const string APP_VERSION = @APP_VERSION@; 16 | const string RESOURCE_BASE = "/com/github/sdv43/whaler"; 17 | const string LOCALE_DIR = @LOCALE_DIR@; 18 | const string DOCKER_ENGINE_SOCKET_PATH = "/run/docker.sock"; 19 | const string DOCKER_ENIGINE_API_VERSION = "1.41"; 20 | } 21 | -------------------------------------------------------------------------------- /src/Utils/DockerContainer.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Docker; 13 | 14 | namespace Utils { 15 | enum DockerContainerType { 16 | GROUP, 17 | CONTAINER 18 | } 19 | 20 | enum DockerContainerState { 21 | UNKNOWN, 22 | PAUSED, 23 | RUNNING, 24 | STOPPED, 25 | } 26 | 27 | class DockerContainer : Object { 28 | public Container? api_container {get; construct set;} 29 | 30 | public string id; 31 | public string name; 32 | public string image; 33 | public bool is_tty; 34 | public DockerContainerType type; 35 | public DockerContainerState state; 36 | 37 | public string? config_path; 38 | public Gee.ArrayList? services; 39 | 40 | public DockerContainer.from_docker_api_container (Container container) { 41 | this.api_container = container; 42 | 43 | this.id = container.id; 44 | this.name = this.format_name (container.name); 45 | this.image = container.image; 46 | this.type = DockerContainerType.CONTAINER; 47 | this.state = this.get_state (container.state); 48 | this.is_tty = false; 49 | } 50 | 51 | public string format_name (string name) { 52 | var value = name; 53 | 54 | if (value[0] == '/') { 55 | value = value.splice (0, 1); 56 | } 57 | 58 | return ucfirst (value); 59 | } 60 | 61 | public DockerContainerState get_state (string state) { 62 | if (state == "running") { 63 | return DockerContainerState.RUNNING; 64 | } 65 | if (state == "paused") { 66 | return DockerContainerState.PAUSED; 67 | } 68 | if (state == "exited") { 69 | return DockerContainerState.STOPPED; 70 | } 71 | 72 | return DockerContainerState.UNKNOWN; 73 | } 74 | 75 | public static bool equal (DockerContainer a, DockerContainer b) { 76 | return a.id == b.id; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Utils/Helpers.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | namespace Utils { 13 | string ucfirst (string str) { 14 | return str.up (1) + str.substring (1); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Utils/HttpClient.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | namespace Utils { 13 | errordomain HttpClientError { 14 | ERROR, 15 | ERROR_ACCESS, 16 | ERROR_NO_ENTRY 17 | } 18 | 19 | enum HttpClientMethod { 20 | GET, 21 | POST, 22 | DELETE, 23 | } 24 | 25 | class HttpClient : Object { 26 | public bool verbose = false; 27 | public string? unix_socket_path {get; set;} 28 | public string? base_url; 29 | 30 | public async HttpClientResponse r_get (string url) throws HttpClientError { 31 | return yield this.request (Utils.HttpClientMethod.GET, url, new HttpClientResponse ()); 32 | } 33 | 34 | public async HttpClientResponse r_post (string url) throws HttpClientError { 35 | return yield this.request (Utils.HttpClientMethod.POST, url, new HttpClientResponse ()); 36 | } 37 | 38 | public async HttpClientResponse r_delete (string url) throws HttpClientError { 39 | return yield this.request (Utils.HttpClientMethod.DELETE, url, new HttpClientResponse ()); 40 | } 41 | 42 | public async HttpClientResponse request (HttpClientMethod method, string url, HttpClientResponse response) throws HttpClientError { 43 | var curl = new Curl.EasyHandle (); 44 | 45 | Curl.Code r; 46 | 47 | r = curl.setopt (Curl.Option.VERBOSE, this.verbose ? 1 : 0); 48 | assert_true (r == Curl.Code.OK); 49 | r = curl.setopt (Curl.Option.URL, (this.base_url ?? "") + url); 50 | assert_true (r == Curl.Code.OK); 51 | r = curl.setopt (Curl.Option.UNIX_SOCKET_PATH, this.unix_socket_path); 52 | assert_true (r == Curl.Code.OK); 53 | r = curl.setopt (Curl.Option.CUSTOMREQUEST, this.get_request_method (method)); 54 | assert_true (r == Curl.Code.OK); 55 | r = curl.setopt (Curl.Option.WRITEDATA, (void*)response.memory_stream); 56 | assert_true (r == Curl.Code.OK); 57 | r = curl.setopt (Curl.Option.WRITEFUNCTION, HttpClientResponse.read_body_data); 58 | assert_true (r == Curl.Code.OK); 59 | 60 | // debug ("call api method: %s - %s", this.get_request_method (method), url); 61 | 62 | yield this.perform (curl); 63 | 64 | long curl_errno = -1; 65 | 66 | r = curl.getinfo (Curl.Info.OS_ERRNO, &curl_errno); 67 | assert_true (r == Curl.Code.OK); 68 | 69 | if (curl_errno == Posix.ENOENT) { 70 | throw new HttpClientError.ERROR_NO_ENTRY (strerror ((int)curl_errno)); 71 | } else if (curl_errno == Posix.EACCES) { 72 | throw new HttpClientError.ERROR_ACCESS (strerror ((int)curl_errno)); 73 | } else if (curl_errno > 0) { 74 | throw new HttpClientError.ERROR ("Unknown error"); 75 | } 76 | 77 | if (r == Curl.Code.OK) { 78 | curl.getinfo (Curl.Info.RESPONSE_CODE, &response.code); 79 | 80 | return response; 81 | } 82 | 83 | throw new HttpClientError.ERROR (Curl.Global.strerror (r)); 84 | } 85 | 86 | public string get_request_method (HttpClientMethod method) { 87 | var result = ""; 88 | 89 | switch (method) { 90 | case HttpClientMethod.GET: 91 | result = "GET"; 92 | break; 93 | 94 | case HttpClientMethod.POST: 95 | result = "POST"; 96 | break; 97 | 98 | case HttpClientMethod.DELETE: 99 | result = "DELETE"; 100 | break; 101 | } 102 | 103 | return result; 104 | } 105 | 106 | private async Curl.Code perform (Curl.EasyHandle curl) throws HttpClientError { 107 | string? err_msg = null; 108 | var r = Curl.Code.OK; 109 | 110 | var task = new Task (this, null, (obj, cl_task) => { 111 | try { 112 | r = (Curl.Code)cl_task.propagate_int (); 113 | } catch (Error error) { 114 | err_msg = error.message; 115 | } finally { 116 | this.perform.callback (); 117 | } 118 | }); 119 | 120 | task.set_task_data (curl, null); 121 | task.run_in_thread ((task, http_client, curl, cancellable) => { 122 | unowned var cl_curl = (Curl.EasyHandle)curl; 123 | 124 | var cl_r = cl_curl.perform (); 125 | task.return_int (cl_r); 126 | }); 127 | 128 | yield; 129 | 130 | if (err_msg != null) { 131 | throw new HttpClientError.ERROR (@"Curl perform error: $err_msg"); 132 | } 133 | 134 | return r; 135 | } 136 | } 137 | 138 | class HttpClientResponse : Object { 139 | public int code; 140 | public MemoryInputStream memory_stream {get; construct set;} 141 | public DataInputStream body_data_stream {get; construct set;} 142 | 143 | public HttpClientResponse() { 144 | this.code = 0; 145 | this.memory_stream = new MemoryInputStream (); 146 | this.body_data_stream = new DataInputStream (this.memory_stream); 147 | } 148 | 149 | public static size_t read_body_data (void* buf, size_t size, size_t nmemb, void* data) { 150 | size_t real_size = size * nmemb; 151 | uint8[] buffer = new uint8[real_size]; 152 | var response_memory_stream = (MemoryInputStream)data; 153 | 154 | Posix.memcpy ((void*)buffer, buf, real_size); 155 | response_memory_stream.add_data (buffer); 156 | 157 | // debug ("http client bytes read: %d", (int)real_size); 158 | 159 | return real_size; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Utils/Sorting/SortingInterface.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | interface Utils.Sorting.SortingInterface : Object { 13 | public abstract string code { get; } 14 | public abstract string name { get; } 15 | 16 | public abstract int compare (DockerContainer a, DockerContainer b); 17 | } 18 | -------------------------------------------------------------------------------- /src/Utils/Sorting/SortingName.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | 14 | class Utils.Sorting.SortingName : Object, SortingInterface { 15 | public string code { 16 | get { 17 | return "name"; 18 | } 19 | } 20 | 21 | public string name { 22 | get { 23 | return _ ("Name"); 24 | } 25 | } 26 | 27 | public int compare (DockerContainer a, DockerContainer b) { 28 | return strcmp (a.name, b.name); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Utils/Sorting/SortingStatus.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | class Utils.Sorting.SortingStatus : Object, SortingInterface { 13 | public string code { 14 | get { 15 | return "status"; 16 | } 17 | } 18 | 19 | public string name { 20 | get { 21 | return _ ("Status"); 22 | } 23 | } 24 | 25 | public int compare (DockerContainer a, DockerContainer b) { 26 | if (this.weight (a) == this.weight (b)) { 27 | return strcmp (a.name, b.name); 28 | } 29 | 30 | return this.weight (a) < this.weight (b) ? +1 : -1; 31 | } 32 | 33 | private int weight (DockerContainer container) { 34 | var weight = 0; 35 | 36 | switch (container.state) { 37 | case DockerContainerState.RUNNING: 38 | weight = 3; 39 | break; 40 | 41 | case DockerContainerState.PAUSED: 42 | weight = 2; 43 | break; 44 | 45 | case DockerContainerState.STOPPED: 46 | weight = 1; 47 | break; 48 | 49 | case DockerContainerState.UNKNOWN: 50 | weight = 0; 51 | break; 52 | } 53 | 54 | return weight; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Utils/Sorting/SortingType.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | class Utils.Sorting.SortingType : Object, SortingInterface { 13 | public string code { 14 | get { 15 | return "type"; 16 | } 17 | } 18 | 19 | public string name { 20 | get { 21 | return _ ("Type"); 22 | } 23 | } 24 | 25 | public int compare (DockerContainer a, DockerContainer b) { 26 | if (this.weight (a) == this.weight (b)) { 27 | return strcmp (a.name, b.name); 28 | } 29 | 30 | return this.weight (a) < this.weight (b) ? -1 : +1; 31 | } 32 | 33 | private int weight (DockerContainer container) { 34 | var weight = 0; 35 | 36 | switch (container.type) { 37 | case DockerContainerType.GROUP: 38 | weight = 1; 39 | break; 40 | 41 | case DockerContainerType.CONTAINER: 42 | weight = 10; 43 | break; 44 | } 45 | 46 | return weight; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Utils/Theme.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils.Constants; 13 | 14 | class Utils.Theme : Object { 15 | public Gtk.CssProvider style_provider; 16 | 17 | public Theme () { 18 | this.style_provider = new Gtk.CssProvider (); 19 | } 20 | 21 | private string get_stylesheet () { 22 | var settings_gtk = Gtk.Settings.get_default (); 23 | var current_icon_theme = settings_gtk.gtk_icon_theme_name; 24 | var target_theme = ""; 25 | 26 | assert_nonnull (settings_gtk); 27 | 28 | switch (current_icon_theme) { 29 | // case "Adwaita": 30 | // target_theme = "adwaita"; 31 | // break; 32 | 33 | // case "Yura": 34 | // target_theme = "yura"; 35 | // break; 36 | 37 | case "elementary": 38 | target_theme = "elementary"; 39 | break; 40 | 41 | default: 42 | target_theme = "elementary"; 43 | settings_gtk.gtk_theme_name = "io.elementary.stylesheet.blueberry"; 44 | settings_gtk.gtk_icon_theme_name = "elementary"; 45 | break; 46 | } 47 | 48 | return target_theme; 49 | } 50 | 51 | private bool is_dark_mode () { 52 | var settings_granite = Granite.Settings.get_default (); 53 | var settings_gtk = Gtk.Settings.get_default (); 54 | 55 | assert_nonnull (settings_gtk); 56 | 57 | var is_dark = settings_granite.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; 58 | settings_gtk.gtk_application_prefer_dark_theme = is_dark; 59 | 60 | return is_dark; 61 | } 62 | 63 | public void apply_styles (Gdk.Screen screen) { 64 | var stylesheet = this.get_stylesheet (); 65 | var mode = this.is_dark_mode () ? "dark" : "light"; 66 | 67 | this.style_provider.load_from_resource (@"$RESOURCE_BASE/style/dist/$stylesheet-$mode.css"); 68 | 69 | Gtk.StyleContext.add_provider_for_screen ( 70 | screen, 71 | this.style_provider, 72 | Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Widgets/HeaderBar.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils.Constants; 13 | 14 | class Widgets.HeaderBar : Gtk.HeaderBar { 15 | public HeaderBar () { 16 | this.show_close_button = true; 17 | this.title = APP_NAME; 18 | 19 | this.pack_start (this.build_button_back ()); 20 | this.pack_end (this.build_button_settings ()); 21 | this.pack_end (this.build_button_refresh ()); 22 | } 23 | 24 | private Gtk.Widget build_button_back () { 25 | var state = State.Root.get_instance (); 26 | var button_back = new Gtk.Button.with_label (_ ("Back")); 27 | 28 | button_back.get_style_context ().add_class ("back-button"); 29 | button_back.valign = Gtk.Align.CENTER; 30 | 31 | button_back.clicked.connect ((button) => { 32 | state.prev_screen (); 33 | }); 34 | button_back.show.connect (() => { 35 | button_back.visible = state.button_back_visible; 36 | }); 37 | 38 | state.notify["button-back-visible"].connect (() => { 39 | button_back.visible = state.button_back_visible; 40 | }); 41 | 42 | return button_back; 43 | } 44 | 45 | private Gtk.Widget build_button_refresh () { 46 | var state = State.Root.get_instance (); 47 | var button_refresh = new Gtk.Button.from_icon_name ("view-refresh-symbolic", Gtk.IconSize.MENU); 48 | 49 | button_refresh.valign = Gtk.Align.CENTER; 50 | button_refresh.focus_on_click = false; 51 | button_refresh.set_tooltip_text (_ ("Update docker container list")); 52 | button_refresh.get_style_context ().add_class ("refresh-button"); 53 | 54 | button_refresh.clicked.connect ((button) => { 55 | button_refresh.get_style_context ().add_class ("refresh-animation"); 56 | 57 | Timeout.add (600, () => { 58 | button_refresh.get_style_context ().remove_class ("refresh-animation"); 59 | 60 | return false; 61 | }, Priority.LOW); 62 | 63 | state.containers_load.begin ((_, res) => { 64 | try { 65 | state.containers_load.end (res); 66 | 67 | if (state.active_screen == ScreenError.CODE) { 68 | state.active_screen = ScreenMain.CODE; 69 | } 70 | } catch (Docker.ApiClientError error) { 71 | var error_widget = ScreenError.build_error_docker_not_avialable ( 72 | error is Docker.ApiClientError.ERROR_NO_ENTRY 73 | ); 74 | 75 | ScreenManager.screen_error_show_widget (error_widget); 76 | } 77 | }); 78 | }); 79 | 80 | button_refresh.show.connect (() => { 81 | button_refresh.sensitive = state.active_screen == ScreenMain.CODE 82 | || state.active_screen == ScreenError.CODE; 83 | }); 84 | 85 | state.notify["active-screen"].connect (() => { 86 | button_refresh.sensitive = state.active_screen == ScreenMain.CODE 87 | || state.active_screen == ScreenError.CODE; 88 | }); 89 | 90 | return button_refresh; 91 | } 92 | 93 | private Gtk.Widget build_button_settings () { 94 | var button_settings = new Gtk.Button.from_icon_name ("open-menu-symbolic", Gtk.IconSize.MENU); 95 | 96 | button_settings.valign = Gtk.Align.CENTER; 97 | button_settings.focus_on_click = false; 98 | button_settings.set_tooltip_text (_ ("Open settings")); 99 | 100 | button_settings.clicked.connect ((button) => { 101 | new Utils.SettingsDialog (); 102 | }); 103 | 104 | return button_settings; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Widgets/ScreenDockerContainer.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | class Widgets.ScreenDockerContainer : Gtk.Box { 13 | public static string CODE = "docker-container"; 14 | 15 | public ScreenDockerContainer () { 16 | this.orientation = Gtk.Orientation.HORIZONTAL; 17 | this.spacing = 0; 18 | 19 | this.get_style_context ().add_class ("screen-docker-container"); 20 | this.pack_start (new Screens.Container.SideBar (), false); 21 | this.pack_end (this.build_log_output (), true, true); 22 | } 23 | 24 | private Gtk.Widget build_log_output () { 25 | var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 26 | 27 | box.pack_start (new Screens.Container.TopBar (), false); 28 | box.pack_end (new Screens.Container.Log (), true, true); 29 | 30 | return box; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Widgets/ScreenError.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | class Widgets.ScreenError : Gtk.Grid { 13 | public static string CODE = "error"; 14 | 15 | public ScreenError () { 16 | this.get_style_context ().add_class ("screen-error"); 17 | this.valign = Gtk.Align.CENTER; 18 | this.halign = Gtk.Align.CENTER; 19 | } 20 | 21 | // public void show_error (string error, string description) { 22 | // this.foreach ((child) => { 23 | // this.remove (child); 24 | // }); 25 | 26 | // var alert = new Granite.Widgets.AlertView (error, description, "dialog-error"); 27 | // alert.get_style_context ().add_class ("alert"); 28 | // this.add (alert); 29 | 30 | // this.show_all (); 31 | // } 32 | 33 | public void show_widget (Gtk.Widget widget) { 34 | this.foreach ((child) => { 35 | this.remove (child); 36 | }); 37 | 38 | this.add (widget); 39 | this.show_all (); 40 | } 41 | 42 | public static Gtk.Widget build_error_docker_not_avialable (bool no_entry) { 43 | var description = _ ( 44 | "It looks like Docker requires root rights to use it. Thus, the application " + 45 | "cannot connect to Docker Engine API. Find out how to run docker without root " + 46 | "rights in Docker Manuals, otherwise the application cannot work correctly. " + 48 | "Or check your socket path to Docker API in Settings." 49 | ); 50 | 51 | if (no_entry) { 52 | description = _ ( 53 | "It looks like Docker is not installed on your system. " + 54 | "To find out how to install it, see Docker Manuals. " + 56 | "Or check your socket path to Docker API in Settings." 57 | ); 58 | } 59 | 60 | var alert = new Granite.Widgets.AlertView ( 61 | _ ("The app cannot connect to Docker API"), 62 | description, 63 | "dialog-error" 64 | ); 65 | 66 | alert.get_style_context ().add_class ("alert"); 67 | alert.show_action (_ ("Open settings")); 68 | alert.action_activated.connect (() => { 69 | new Utils.SettingsDialog (); 70 | }); 71 | 72 | return alert; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Widgets/ScreenMain.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Widgets.Screens.Main; 13 | 14 | class Widgets.ScreenMain : Gtk.Box { 15 | public const string CODE = "main"; 16 | 17 | public ScreenMain () { 18 | this.orientation = Gtk.Orientation.VERTICAL; 19 | this.spacing = 0; 20 | 21 | this.get_style_context ().add_class ("screen-main"); 22 | this.pack_start (new ContainersGridFilter (), false, false); 23 | this.pack_start (new ContainersGrid (), true, true); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Widgets/ScreenManager.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | class Widgets.ScreenManager : Gtk.Overlay { 13 | private static ScreenManager? instance; 14 | private ScreenError screen_error; 15 | private Granite.Widgets.OverlayBar overlay_bar; 16 | private bool overlay_bar_visible = false; 17 | 18 | private ScreenManager () { 19 | var state = State.Root.get_instance (); 20 | 21 | this.overlay_bar = new Granite.Widgets.OverlayBar (this); 22 | this.overlay_bar.active = true; 23 | 24 | this.screen_error = new ScreenError (); 25 | 26 | var stack = new Gtk.Stack (); 27 | stack.transition_type = Gtk.StackTransitionType.OVER_LEFT_RIGHT; 28 | stack.transition_duration = 300; 29 | stack.add_named (this.screen_error, ScreenError.CODE); 30 | stack.add_named (new ScreenMain (), ScreenMain.CODE); 31 | stack.add_named (new ScreenDockerContainer (), ScreenDockerContainer.CODE); 32 | 33 | stack.show.connect (() => { 34 | stack.set_visible_child_name (state.active_screen); 35 | }); 36 | 37 | state.notify["active-screen"].connect (() => { 38 | stack.set_visible_child_name (state.active_screen); 39 | }); 40 | 41 | this.show.connect (() => { 42 | this.overlay_bar.visible = this.overlay_bar_visible; 43 | }); 44 | 45 | this.add (stack); 46 | } 47 | 48 | public static ScreenManager get_instance () { 49 | if (ScreenManager.instance == null) { 50 | ScreenManager.instance = new ScreenManager (); 51 | } 52 | 53 | return ScreenManager.instance; 54 | } 55 | 56 | public static void overlay_bar_show (string message, int delay = 1000) { 57 | instance.overlay_bar_visible = true; 58 | instance.overlay_bar.label = message; 59 | 60 | Timeout.add (delay, () => { 61 | instance.overlay_bar.visible = instance.overlay_bar_visible; 62 | 63 | return false; 64 | }); 65 | } 66 | 67 | public static void overlay_bar_hide () { 68 | instance.overlay_bar_visible = false; 69 | instance.overlay_bar.visible = instance.overlay_bar_visible; 70 | } 71 | 72 | public static void dialog_error_show (string title, string description, string icon = "dialog-error") { 73 | var message_dialog = new Granite.MessageDialog.with_image_from_icon_name ( 74 | title, 75 | description, 76 | icon, 77 | Gtk.ButtonsType.CLOSE 78 | ); 79 | 80 | message_dialog.transient_for = Whaler.get_instance ().active_window; 81 | message_dialog.run (); 82 | message_dialog.destroy (); 83 | } 84 | 85 | // public static void screen_error_show (string error, string description) { 86 | // instance.screen_error.show_error (error, description); 87 | // State.Root.get_instance ().active_screen = ScreenError.CODE; 88 | // } 89 | 90 | public static void screen_error_show_widget (Gtk.Widget widget) { 91 | instance.screen_error.show_widget (widget); 92 | State.Root.get_instance ().active_screen = ScreenError.CODE; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Widgets/Screens/DockerContainer/Log.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils.Constants; 13 | 14 | class Widgets.Screens.Container.Log : Gtk.Overlay { 15 | public Log () { 16 | this.add (new LogOutput ()); 17 | this.add_overlay (this.build_switcher_container ()); 18 | } 19 | 20 | private Gtk.Widget build_switcher_container () { 21 | var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); 22 | box.get_style_context ().add_class ("auto-scroll"); 23 | box.valign = Gtk.Align.CENTER; 24 | box.halign = Gtk.Align.CENTER; 25 | box.pack_start (this.build_switcher_label (), false); 26 | box.pack_end (this.build_switcher (box), false); 27 | 28 | var event_box = new Gtk.EventBox (); 29 | event_box.valign = Gtk.Align.START; 30 | event_box.halign = Gtk.Align.END; 31 | event_box.set_events (box.get_events () | Gdk.EventMask.ENTER_NOTIFY_MASK); 32 | event_box.add (box); 33 | 34 | event_box.enter_notify_event.connect ((e) => { 35 | box.get_style_context ().add_class ("visible"); 36 | 37 | return false; 38 | }); 39 | 40 | event_box.leave_notify_event.connect ((e) => { 41 | box.get_style_context ().remove_class ("visible"); 42 | 43 | return false; 44 | }); 45 | 46 | return event_box; 47 | } 48 | 49 | private Gtk.Widget build_switcher_label () { 50 | var label = new Gtk.Label (_ ("Autoscroll") + ":"); 51 | label.valign = Gtk.Align.CENTER; 52 | label.halign = Gtk.Align.END; 53 | 54 | return label; 55 | } 56 | 57 | private Gtk.Widget build_switcher (Gtk.Widget box) { 58 | var switcher = new Gtk.Switch (); 59 | switcher.valign = Gtk.Align.CENTER; 60 | switcher.halign = Gtk.Align.START; 61 | switcher.get_style_context ().add_class ("auto-scroll-switcher"); 62 | switcher.set_tooltip_text (_ ("Enable autoscroll to bottom border")); 63 | 64 | var settings = new Settings (APP_ID); 65 | settings.bind ("screen-docker-container-autoscroll", switcher, "active", SettingsBindFlags.DEFAULT); 66 | 67 | switcher.enter_notify_event.connect ((e) => { 68 | box.get_style_context ().add_class ("visible"); 69 | 70 | return false; 71 | }); 72 | 73 | switcher.leave_notify_event.connect ((e) => { 74 | box.get_style_context ().remove_class ("visible"); 75 | 76 | return false; 77 | }); 78 | 79 | var state_root = State.Root.get_instance (); 80 | state_root.screen_docker_container.is_auto_scroll_enable = switcher.active; 81 | 82 | switcher.notify["active"].connect (() => { 83 | state_root.screen_docker_container.is_auto_scroll_enable = switcher.active; 84 | }); 85 | 86 | return switcher; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Widgets/Screens/DockerContainer/LogOutput.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | using Docker; 14 | 15 | class Widgets.Screens.Container.LogOutput : Gtk.ScrolledWindow { 16 | public LogOutput () { 17 | var state_root = State.Root.get_instance (); 18 | var state_docker_container = state_root.screen_docker_container; 19 | var text_view = new Gtk.TextView (); 20 | var log_buffer = new Gtk.TextBuffer (null); 21 | 22 | this.get_style_context ().add_class ("log-output"); 23 | 24 | text_view.get_style_context ().add_class ("terminal"); 25 | text_view.editable = false; 26 | text_view.cursor_visible = false; 27 | text_view.buffer = log_buffer; 28 | this.add (text_view); 29 | 30 | this.vadjustment.changed.connect (() => { 31 | if (state_docker_container.is_auto_scroll_enable) { 32 | this.vadjustment.value = this.vadjustment.upper - this.vadjustment.page_size; 33 | } 34 | }); 35 | 36 | ContainerLogWatcher? log_watcher = null; 37 | DockerContainer? selected_container = null; 38 | 39 | state_docker_container.notify["service"].connect (() => { 40 | var is_container_changed = true; 41 | 42 | if (selected_container != null) { 43 | is_container_changed = selected_container.id != state_docker_container.service.id; 44 | } 45 | 46 | selected_container = state_docker_container.service; 47 | 48 | if (is_container_changed) { 49 | if (log_watcher != null) { 50 | log_watcher.watching_stop (); 51 | log_buffer.text = ""; 52 | } 53 | 54 | log_watcher = new ContainerLogWatcher (selected_container, log_buffer); 55 | log_watcher.watching_start (); 56 | } else { 57 | log_watcher.watching_start (); 58 | } 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Widgets/Screens/DockerContainer/SideBar.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | 14 | class Widgets.Screens.Container.SideBar : Gtk.ScrolledWindow { 15 | public SideBar () { 16 | var state = State.Root.get_instance ().screen_docker_container; 17 | var list_box = new Gtk.ListBox (); 18 | 19 | this.width_request = 300; 20 | this.get_style_context ().add_class ("side-bar"); 21 | this.add (list_box); 22 | 23 | list_box.activate_on_single_click = false; 24 | list_box.selection_mode = Gtk.SelectionMode.SINGLE; 25 | list_box.row_selected.connect ((row) => { 26 | if (row.get_index () == -1) { 27 | return; 28 | } 29 | 30 | var side_bar_item = row as SideBarItem; 31 | assert_nonnull (side_bar_item); 32 | 33 | state.service = side_bar_item.service; 34 | }); 35 | 36 | state.notify["container"].connect (() => { 37 | this.visible = state.container.type == DockerContainerType.GROUP; 38 | 39 | if (!this.visible) { 40 | return; 41 | } 42 | 43 | list_box.foreach ((child) => { 44 | list_box.remove (child); 45 | }); 46 | 47 | // 48 | var main_container_item = new SideBarItem (state.container); 49 | var separator_item = new SideBarSeparator (_ ("Services")); 50 | 51 | list_box.add (main_container_item); 52 | list_box.add (separator_item); 53 | 54 | var selected_item = main_container_item; 55 | 56 | foreach (var service in state.container.services) { 57 | var item = new SideBarItem (service); 58 | 59 | list_box.add (item); 60 | 61 | if (state.service != null && state.service.id == service.id) { 62 | selected_item = item; 63 | } 64 | } 65 | 66 | state.service = selected_item.service; 67 | 68 | list_box.select_row (selected_item); 69 | list_box.show_all (); 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Widgets/Screens/DockerContainer/SideBarItem.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | 14 | class Widgets.Screens.Container.SideBarItem : Gtk.ListBoxRow { 15 | public DockerContainer service; 16 | 17 | public SideBarItem (DockerContainer service) { 18 | var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 19 | 20 | this.service = service; 21 | this.activatable = false; 22 | this.selectable = true; 23 | this.get_style_context ().add_class ("side-bar-item"); 24 | this.add (box); 25 | 26 | // 27 | var container_name = new Gtk.Label (service.name); 28 | 29 | container_name.get_style_context ().add_class ("primary"); 30 | container_name.get_style_context ().add_class ("name"); 31 | container_name.max_width_chars = 16; 32 | container_name.ellipsize = Pango.EllipsizeMode.END; 33 | container_name.halign = Gtk.Align.START; 34 | box.pack_start (container_name, false); 35 | 36 | // 37 | var container_image = new Gtk.Label (service.image); 38 | 39 | container_image.get_style_context ().add_class ("dim-label"); 40 | container_image.get_style_context ().add_class ("image"); 41 | container_image.max_width_chars = 16; 42 | container_image.ellipsize = Pango.EllipsizeMode.END; 43 | container_image.halign = Gtk.Align.START; 44 | box.pack_end (container_image, false); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Widgets/Screens/DockerContainer/SideBarSeparator.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | class Widgets.Screens.Container.SideBarSeparator : Gtk.ListBoxRow { 13 | public SideBarSeparator (string text) { 14 | this.can_focus = false; 15 | this.activatable = false; 16 | this.selectable = false; 17 | this.get_style_context ().add_class ("side-bar-separator"); 18 | this.get_style_context ().add_class ("h4"); 19 | 20 | // 21 | var label = new Gtk.Label (text); 22 | 23 | label.max_width_chars = 16; 24 | label.ellipsize = Pango.EllipsizeMode.END; 25 | label.halign = Gtk.Align.START; 26 | 27 | this.add (label); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Widgets/Screens/DockerContainer/TopBar.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | 14 | namespace Widgets.Screens.Container { 15 | class TopBar : Gtk.Box { 16 | private DockerContainer container; 17 | 18 | public TopBar () { 19 | var state_docker_container = State.Root.get_instance ().screen_docker_container; 20 | 21 | this.orientation = Gtk.Orientation.HORIZONTAL; 22 | this.spacing = 0; 23 | 24 | this.get_style_context ().add_class ("top-bar"); 25 | 26 | state_docker_container.notify["service"].connect (() => { 27 | this.foreach ((child) => { 28 | this.remove (child); 29 | }); 30 | 31 | this.container = state_docker_container.service; 32 | assert_nonnull (this.container); 33 | 34 | this.pack_start (this.build_container_block (), false); 35 | this.pack_end (this.build_container_actions (), false); 36 | this.show_all (); 37 | }); 38 | } 39 | 40 | private Gtk.Widget build_container_block () { 41 | var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 42 | 43 | box.pack_start (this.build_container_name (), false); 44 | box.pack_end (this.build_container_status_label () ?? this.build_container_image (), false); 45 | 46 | return box; 47 | } 48 | 49 | private Gtk.Widget build_container_name () { 50 | var container_name = new Gtk.Label (this.container.name); 51 | container_name.get_style_context ().add_class ("primary"); 52 | container_name.get_style_context ().add_class ("container-name"); 53 | container_name.halign = Gtk.Align.START; 54 | 55 | return container_name; 56 | } 57 | 58 | private Gtk.Widget? build_container_status_label () { 59 | var label = Utils.DockerContainerStatusLabel.create_by_container (this.container); 60 | 61 | if (label == null) { 62 | return null; 63 | } 64 | 65 | label.halign = Gtk.Align.START; 66 | 67 | return label; 68 | } 69 | 70 | private Gtk.Widget build_container_image () { 71 | var label = new Gtk.Label (this.container.image); 72 | label.get_style_context ().add_class ("container-image"); 73 | label.halign = Gtk.Align.START; 74 | 75 | return label; 76 | } 77 | 78 | private Gtk.Widget build_container_actions () { 79 | var actions = new TopBarActions (this.container); 80 | actions.valign = Gtk.Align.CENTER; 81 | 82 | return actions; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Widgets/Screens/DockerContainer/TopBarActions.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | using Widgets.Screens.Main; 14 | 15 | namespace Widgets.Screens.Container { 16 | class TopBarActions : Gtk.Box { 17 | private DockerContainer container; 18 | 19 | public TopBarActions (DockerContainer container) { 20 | this.container = container; 21 | this.sensitive = container.state != DockerContainerState.UNKNOWN; 22 | this.orientation = Gtk.Orientation.HORIZONTAL; 23 | this.spacing = 0; 24 | this.pack_start (this.build_button_main_action (), false); 25 | this.pack_end (this.build_button_menu_action (), false); 26 | } 27 | 28 | private Gtk.Widget build_button_main_action () { 29 | var icon_name = "media-playback-start-symbolic"; 30 | var label_text = _ ("Start"); 31 | 32 | if (this.container.state == DockerContainerState.RUNNING) { 33 | icon_name = "media-playback-stop-symbolic"; 34 | label_text = _ ("Stop"); 35 | } else if (this.container.state == DockerContainerState.PAUSED) { 36 | label_text = _ ("Unpause"); 37 | } 38 | 39 | var icon = new Gtk.Image.from_icon_name (icon_name, Gtk.IconSize.BUTTON); 40 | var label = new Gtk.Label (label_text); 41 | 42 | var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); 43 | box.pack_start (icon, false); 44 | box.pack_end (label, false); 45 | 46 | var button = new Gtk.Button (); 47 | button.get_style_context ().add_class ("button-main-action"); 48 | button.add (box); 49 | button.clicked.connect (() => { 50 | this.sensitive = false; 51 | 52 | ContainerCardActions.button_main_action_handler.begin (this.container, (_, res) => { 53 | ContainerCardActions.button_main_action_handler.end (res); 54 | this.sensitive = true; 55 | }); 56 | }); 57 | 58 | return button; 59 | } 60 | 61 | private Gtk.Widget build_button_menu_action () { 62 | var button = new Gtk.Button (); 63 | var menu = ContainerCardActions.build_menu (this.container, this); 64 | 65 | button.get_style_context ().add_class ("button-menu"); 66 | button.valign = Gtk.Align.FILL; 67 | button.clicked.connect ((widget) => { 68 | menu.popup_at_widget ( 69 | widget, 70 | Gdk.Gravity.NORTH_WEST, 71 | Gdk.Gravity.NORTH_WEST, 72 | null 73 | ); 74 | }); 75 | 76 | var icon = new Gtk.Image.from_icon_name ("pan-down-symbolic", Gtk.IconSize.BUTTON); 77 | button.add (icon); 78 | 79 | return button; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Widgets/Screens/Main/ContainerCard.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | 14 | class Widgets.Screens.Main.ContainerCard : Gtk.FlowBoxChild { 15 | private DockerContainer container; 16 | 17 | public ContainerCard (DockerContainer container) { 18 | this.container = container; 19 | 20 | var card_actions = new ContainerCardActions (container); 21 | card_actions.hexpand = true; 22 | card_actions.halign = Gtk.Align.END; 23 | 24 | var grid = new Gtk.Grid (); 25 | grid.attach (this.build_container_icon (), 1, 1, 1, 2); 26 | grid.attach (this.build_container_name (), 2, 1, 1, 1); 27 | grid.attach (this.build_container_status_label () ?? this.build_container_image (), 2, 2, 1, 1); 28 | grid.attach (card_actions, 3, 1, 1, 2); 29 | 30 | this.get_style_context ().add_class ("docker-container"); 31 | this.add (grid); 32 | 33 | if (container.state == DockerContainerState.UNKNOWN) { 34 | this.sensitive = false; 35 | } 36 | } 37 | 38 | private Gtk.Widget build_container_name () { 39 | var label = new Gtk.Label (this.container.name); 40 | 41 | label.get_style_context ().add_class ("primary"); 42 | label.get_style_context ().add_class ("docker-container-name"); 43 | label.max_width_chars = 16; 44 | label.ellipsize = Pango.EllipsizeMode.END; 45 | label.halign = Gtk.Align.START; 46 | label.valign = Gtk.Align.END; 47 | 48 | return label; 49 | } 50 | 51 | private Gtk.Widget? build_container_status_label () { 52 | var info = Utils.DockerContainerStatusLabel.create_by_container (this.container); 53 | 54 | if (info == null) { 55 | return null; 56 | } 57 | 58 | info.halign = Gtk.Align.START; 59 | info.valign = Gtk.Align.START; 60 | 61 | return info; 62 | } 63 | 64 | private Gtk.Widget build_container_image () { 65 | var label = new Gtk.Label (this.container.image); 66 | 67 | label.get_style_context ().add_class ("docker-container-image"); 68 | label.max_width_chars = 16; 69 | label.ellipsize = Pango.EllipsizeMode.END; 70 | label.halign = Gtk.Align.START; 71 | label.valign = Gtk.Align.START; 72 | 73 | return label; 74 | } 75 | 76 | private Gtk.Widget build_container_icon () { 77 | var icon_name = "docker-container-symbolic"; 78 | 79 | if (this.container.type == DockerContainerType.GROUP) { 80 | icon_name = "docker-container-group-symbolic"; 81 | } 82 | 83 | var image = new Gtk.Image.from_icon_name (icon_name, Gtk.IconSize.DIALOG); 84 | image.get_style_context ().add_class ("docker-container-preview-image"); 85 | image.set_pixel_size (56); 86 | 87 | return image; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Widgets/Screens/Main/ContainerCardActions.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | 14 | class Widgets.Screens.Main.ContainerCardActions : Gtk.Box { 15 | private DockerContainer container; 16 | 17 | public ContainerCardActions (DockerContainer container) { 18 | this.container = container; 19 | this.orientation = Gtk.Orientation.HORIZONTAL; 20 | this.spacing = 0; 21 | this.get_style_context ().add_class ("docker-container-actions"); 22 | this.pack_start (this.build_button_main_action (), false, false); 23 | this.pack_start (this.build_button_menu_action (), false, false); 24 | } 25 | 26 | private Gtk.Widget build_button_main_action () { 27 | var icon_name = "media-playback-start-symbolic"; 28 | 29 | if (this.container.state == DockerContainerState.RUNNING) { 30 | icon_name = "media-playback-stop-symbolic"; 31 | } 32 | 33 | var button = new Gtk.Button.from_icon_name (icon_name, Gtk.IconSize.MENU); 34 | button.valign = Gtk.Align.CENTER; 35 | button.clicked.connect (() => { 36 | this.sensitive = false; 37 | 38 | ContainerCardActions.button_main_action_handler.begin (this.container, (_, res) => { 39 | ContainerCardActions.button_main_action_handler.end (res); 40 | this.sensitive = true; 41 | }); 42 | }); 43 | 44 | return button; 45 | } 46 | 47 | private Gtk.Widget build_button_menu_action () { 48 | var menu = ContainerCardActions.build_menu (this.container, this); 49 | 50 | var button = new Gtk.Button.from_icon_name ("view-more-symbolic", Gtk.IconSize.BUTTON); 51 | button.valign = Gtk.Align.CENTER; 52 | button.clicked.connect ((widget) => { 53 | menu.popup_at_widget ( 54 | widget, 55 | Gdk.Gravity.NORTH_WEST, 56 | Gdk.Gravity.NORTH_WEST, 57 | null 58 | ); 59 | }); 60 | 61 | return button; 62 | } 63 | 64 | public static Gtk.Menu build_menu (DockerContainer container, Gtk.Widget actions_widget) { 65 | var state = State.Root.get_instance (); 66 | 67 | var item_pause = new Gtk.MenuItem.with_label (_ ("Pause")); 68 | item_pause.sensitive = container.state == DockerContainerState.RUNNING; 69 | item_pause.activate.connect (() => { 70 | var err_msg = _ ("Container pause error"); 71 | actions_widget.sensitive = false; 72 | 73 | state.container_pause.begin (container, (_, res) => { 74 | try { 75 | state.container_pause.end (res); 76 | } catch (Docker.ApiClientError error) { 77 | ScreenManager.dialog_error_show (err_msg, error.message); 78 | } finally { 79 | actions_widget.sensitive = true; 80 | } 81 | }); 82 | }); 83 | item_pause.show (); 84 | 85 | var item_restart = new Gtk.MenuItem.with_label (_ ("Restart")); 86 | item_restart.activate.connect (() => { 87 | var err_msg = _ ("Container restart error"); 88 | 89 | actions_widget.sensitive = false; 90 | ScreenManager.overlay_bar_show (_ ("Restarting container")); 91 | 92 | state.container_restart.begin (container, (_, res) => { 93 | try { 94 | state.container_restart.end (res); 95 | } catch (Docker.ApiClientError error) { 96 | ScreenManager.dialog_error_show (err_msg, error.message); 97 | } finally { 98 | actions_widget.sensitive = true; 99 | ScreenManager.overlay_bar_hide (); 100 | } 101 | }); 102 | }); 103 | item_restart.show (); 104 | 105 | var item_remove = new Gtk.MenuItem.with_label (_ ("Remove")); 106 | item_remove.activate.connect (() => { 107 | var confirm = new Utils.ConfirmationDialog ( 108 | _ ("Do you really want to remove container?"), 109 | _ ("Yes, remove"), 110 | _ ("Cancel") 111 | ); 112 | 113 | actions_widget.sensitive = false; 114 | 115 | confirm.accept.connect (() => { 116 | var err_msg = _ ("Container remove error"); 117 | 118 | ScreenManager.overlay_bar_show (_ ("Removing container")); 119 | 120 | state.container_remove.begin (container, (_, res) => { 121 | try { 122 | state.container_remove.end (res); 123 | } catch (Docker.ApiClientError error) { 124 | ScreenManager.dialog_error_show (err_msg, error.message); 125 | } finally { 126 | actions_widget.sensitive = true; 127 | ScreenManager.overlay_bar_hide (); 128 | } 129 | }); 130 | }); 131 | 132 | confirm.cancel.connect (() => { 133 | actions_widget.sensitive = true; 134 | }); 135 | }); 136 | item_remove.show (); 137 | 138 | var item_info = new Gtk.MenuItem.with_label (_ ("Info")); 139 | item_info.activate.connect (() => { 140 | var err_msg = _ ("Cannot get information"); 141 | 142 | state.container_inspect.begin (container, (_, res) => { 143 | try { 144 | new Utils.ContainerInfoDialog (state.container_inspect.end (res)); 145 | } catch (Docker.ApiClientError error) { 146 | ScreenManager.dialog_error_show (err_msg, error.message); 147 | } 148 | }); 149 | }); 150 | item_info.show (); 151 | 152 | var menu = new Gtk.Menu (); 153 | menu.append (item_pause); 154 | menu.append (item_restart); 155 | menu.append (item_remove); 156 | menu.append (item_info); 157 | 158 | return menu; 159 | } 160 | 161 | public static async void button_main_action_handler (DockerContainer container) { 162 | var state = State.Root.get_instance (); 163 | var err_msg = _ ("Container action error"); 164 | 165 | try { 166 | switch (container.state) { 167 | case DockerContainerState.RUNNING: 168 | err_msg = _ ("Container stop error"); 169 | ScreenManager.overlay_bar_show (_ ("Stopping container")); 170 | yield state.container_stop(container); 171 | break; 172 | 173 | case DockerContainerState.STOPPED: 174 | err_msg = _ ("Container start error"); 175 | ScreenManager.overlay_bar_show (_ ("Starting container")); 176 | yield state.container_start(container); 177 | break; 178 | 179 | case DockerContainerState.PAUSED: 180 | err_msg = _ ("Container unpause error"); 181 | yield state.container_unpause(container); 182 | break; 183 | 184 | case DockerContainerState.UNKNOWN: 185 | ScreenManager.dialog_error_show (err_msg, _ ("Container state is unknown")); 186 | break; 187 | } 188 | } catch (Docker.ApiClientError error) { 189 | ScreenManager.dialog_error_show (err_msg, error.message); 190 | } finally { 191 | ScreenManager.overlay_bar_hide (); 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/Widgets/Screens/Main/ContainersGrid.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | class Widgets.Screens.Main.ContainersGrid : Gtk.Stack { 13 | public const string CODE = "main"; 14 | 15 | private signal void container_cards_updated(); 16 | 17 | public ContainersGrid () { 18 | var state = State.Root.get_instance (); 19 | var state_main = state.screen_main; 20 | 21 | this.add_named (this.build_loader (), "loader"); 22 | this.add_named (this.build_notice (), "no-containers"); 23 | this.add_named (this.build_grid (), "containers"); 24 | 25 | state_main.notify["containers-prepared"].connect (() => { 26 | if (state_main.containers_prepared.size > 0) { 27 | this.set_visible_child_name ("containers"); 28 | 29 | if (state.active_screen == ContainersGrid.CODE) { 30 | this.container_cards_updated (); 31 | } 32 | } else { 33 | this.set_visible_child_name ("no-containers"); 34 | } 35 | }); 36 | 37 | state.notify["active-screen"].connect (() => { 38 | if (state.active_screen == ContainersGrid.CODE) { 39 | this.container_cards_updated (); 40 | } 41 | }); 42 | } 43 | 44 | private Gtk.Widget build_grid () { 45 | var state = State.Root.get_instance (); 46 | var state_main = state.screen_main; 47 | var root = new Gtk.ScrolledWindow (null, null); 48 | 49 | var flow_box = new Gtk.FlowBox (); 50 | flow_box.get_style_context ().add_class ("docker-containers-grid"); 51 | flow_box.homogeneous = true; 52 | flow_box.valign = Gtk.Align.START; 53 | flow_box.min_children_per_line = 2; 54 | flow_box.max_children_per_line = 7; 55 | flow_box.selection_mode = Gtk.SelectionMode.NONE; 56 | flow_box.activate_on_single_click = true; 57 | flow_box.child_activated.connect ((child) => { 58 | state.screen_docker_container.container = state_main.containers_prepared[child.get_index ()]; 59 | state.next_screen (Widgets.ScreenDockerContainer.CODE); 60 | }); 61 | root.add (flow_box); 62 | 63 | this.container_cards_updated.connect (() => { 64 | flow_box.foreach ((child) => { 65 | flow_box.remove (child); 66 | }); 67 | 68 | foreach (var container in state_main.containers_prepared) { 69 | flow_box.add (new ContainerCard (container)); 70 | } 71 | 72 | flow_box.show_all (); 73 | }); 74 | 75 | return root; 76 | } 77 | 78 | private Gtk.Widget build_loader () { 79 | var loader = new Gtk.Spinner (); 80 | 81 | loader.width_request = 32; 82 | loader.height_request = 32; 83 | loader.halign = Gtk.Align.CENTER; 84 | loader.valign = Gtk.Align.CENTER; 85 | 86 | loader.start (); 87 | 88 | return loader; 89 | } 90 | 91 | private Gtk.Widget build_notice () { 92 | var label = new Gtk.Label (_ ("No containers")); 93 | 94 | label.get_style_context ().add_class ("h3"); 95 | label.halign = Gtk.Align.CENTER; 96 | label.valign = Gtk.Align.CENTER; 97 | 98 | return label; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Widgets/Screens/Main/ContainersGridFilter.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils.Sorting; 13 | using Utils.Constants; 14 | 15 | class Widgets.Screens.Main.ContainersGridFilter : Gtk.Box { 16 | public ContainersGridFilter () { 17 | var first_launch = true; 18 | var state = State.Root.get_instance (); 19 | var search_entry = this.build_search_entry (); 20 | 21 | this.sensitive = false; 22 | this.orientation = Gtk.Orientation.HORIZONTAL; 23 | this.spacing = 0; 24 | 25 | this.get_style_context ().add_class ("docker-containers-filter"); 26 | this.pack_start (search_entry, false, false); 27 | this.pack_end (this.build_sorting_combobox (), false, false); 28 | 29 | state.notify["containers"].connect (() => { 30 | this.sensitive = state.containers.size > 0; 31 | 32 | if (this.sensitive && first_launch) { 33 | search_entry.grab_focus (); 34 | first_launch = false; 35 | } 36 | }); 37 | } 38 | 39 | private Gtk.Widget build_search_entry () { 40 | var state = State.Root.get_instance (); 41 | var entry = new Gtk.SearchEntry (); 42 | var settings = new Settings (APP_ID); 43 | 44 | entry.width_request = 240; 45 | entry.search_changed.connect (() => { 46 | state.screen_main.search_term = entry.text.down (entry.text.length); 47 | }); 48 | 49 | settings.bind ("main-screen-search-term", entry, "text", SettingsBindFlags.DEFAULT); 50 | 51 | return entry; 52 | } 53 | 54 | private Gtk.Widget build_sorting_combobox () { 55 | SortingInterface[] sortings = { 56 | new SortingStatus (), 57 | new SortingName (), 58 | new SortingType () 59 | }; 60 | 61 | var state = State.Root.get_instance (); 62 | var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); 63 | 64 | var label = new Gtk.Label (_ ("Sort by:")); 65 | box.pack_start (label, false, false, 12); 66 | 67 | var combo = new Gtk.ComboBoxText (); 68 | foreach (var sorting in sortings) { 69 | combo.append_text (sorting.name); 70 | } 71 | box.pack_end (combo, false, false); 72 | 73 | combo.changed.connect ((combo) => { 74 | state.screen_main.sorting = sortings[combo.active]; 75 | }); 76 | 77 | var settings = new Settings (APP_ID); 78 | settings.bind ("main-screen-sorting", combo, "active", SettingsBindFlags.DEFAULT); 79 | 80 | return box; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/Widgets/Utils/ConfirmationDialog.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | class Widgets.Utils.ConfirmationDialog : Granite.Dialog { 13 | public signal void cancel(); 14 | public signal void accept(); 15 | 16 | public ConfirmationDialog (string question, string yes, string no) { 17 | this.transient_for = Whaler.get_instance ().active_window; 18 | this.transient_for.sensitive = false; 19 | this.skip_taskbar_hint = true; 20 | this.add_button (no, Gtk.ResponseType.CANCEL); 21 | this.get_content_area ().add (this.build_question (question)); 22 | 23 | var button_yes = this.add_button (yes, Gtk.ResponseType.ACCEPT); 24 | button_yes.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION); 25 | 26 | this.response.connect ((resp_id) => { 27 | if (resp_id == Gtk.ResponseType.ACCEPT) { 28 | this.accept (); 29 | } else { 30 | this.cancel (); 31 | } 32 | 33 | this.transient_for.sensitive = true; 34 | this.destroy (); 35 | }); 36 | 37 | this.show_all (); 38 | } 39 | 40 | private Gtk.Widget build_question (string question) { 41 | var label = new Gtk.Label (question); 42 | 43 | label.get_style_context ().add_class ("h3"); 44 | label.get_style_context ().add_class ("confirmation-question"); 45 | 46 | return label; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Widgets/Utils/ContainerInfoDialog.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | using Docker; 14 | 15 | class Widgets.Utils.ContainerInfoDialog : Granite.Dialog { 16 | Gee.HashMap containers_info; 17 | 18 | public ContainerInfoDialog (Gee.HashMap containers_info) { 19 | this.default_width = 460; 20 | this.default_height = 400; 21 | this.containers_info = containers_info; 22 | this.skip_taskbar_hint = true; 23 | this.transient_for = Whaler.get_instance ().active_window; 24 | this.add_button (_ ("Close"), Gtk.ResponseType.CANCEL); 25 | this.get_content_area ().add (this.build_content_area ()); 26 | 27 | this.response.connect ((resp_id) => { 28 | this.destroy (); 29 | }); 30 | 31 | this.show_all (); 32 | } 33 | 34 | private Gtk.Widget build_content_area () { 35 | var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 36 | box.expand = true; 37 | 38 | var stack = new Gtk.Stack (); 39 | foreach (var entry in this.containers_info) { 40 | var container = entry.key; 41 | var info = entry.value; 42 | 43 | stack.add_titled (this.build_tab (info), container.id, container.name); 44 | } 45 | box.pack_end (stack); 46 | 47 | if (this.containers_info.size > 1) { 48 | var switcher = new Gtk.StackSwitcher (); 49 | switcher.get_style_context ().add_class ("container-info-dialog-switcher"); 50 | switcher.stack = stack; 51 | switcher.halign = Gtk.Align.CENTER; 52 | box.pack_start (switcher, false); 53 | } 54 | 55 | return box; 56 | } 57 | 58 | private Gtk.Widget build_tab (ContainerInspectInfo info) { 59 | var scrolled_window = new Gtk.ScrolledWindow (null, null); 60 | var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 61 | 62 | box.get_style_context ().add_class ("container-info-dialog-tab"); 63 | box.pack_start (this.build_row (_ ("Name"), {info.name}), false); 64 | box.pack_start (this.build_row (_ ("Image"), {info.image}), false); 65 | box.pack_start (this.build_row (_ ("Status"), {info.status}), false); 66 | 67 | if (info.ports != null) { 68 | box.pack_start (this.build_row (_ ("Ports"), info.ports), false); 69 | } 70 | 71 | if (info.binds != null) { 72 | box.pack_start (this.build_row (_ ("Binds"), info.binds), false); 73 | } 74 | 75 | if (info.envs != null) { 76 | box.pack_start (this.build_row (_ ("Env"), info.envs), false); 77 | } 78 | 79 | scrolled_window.add (box); 80 | 81 | return scrolled_window; 82 | } 83 | 84 | private Gtk.Widget build_row (string label, string[] values) { 85 | var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); 86 | 87 | box.get_style_context ().add_class ("container-info-dialog-row"); 88 | box.pack_start (this.build_label (label), false); 89 | box.pack_start (this.build_values (values), false); 90 | 91 | return box; 92 | } 93 | 94 | private Gtk.Widget build_label (string text) { 95 | var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); 96 | box.width_request = 140; 97 | 98 | var label = new Gtk.Label (text); 99 | label.get_style_context ().add_class ("container-info-dialog-label"); 100 | label.halign = Gtk.Align.END; 101 | label.valign = Gtk.Align.START; 102 | 103 | box.pack_end (label, false); 104 | 105 | return box; 106 | } 107 | 108 | private Gtk.Widget build_values (string[] values) { 109 | if (values.length == 1) { 110 | return this.build_value (values[0]); 111 | } 112 | 113 | var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 114 | 115 | foreach (var value in values) { 116 | box.pack_end (this.build_value (value), false); 117 | } 118 | 119 | return box; 120 | } 121 | 122 | private Gtk.Widget build_value (string value) { 123 | var label = new Gtk.Label (value); 124 | 125 | label.get_style_context ().add_class ("container-info-dialog-value"); 126 | label.halign = Gtk.Align.START; 127 | label.selectable = true; 128 | label.single_line_mode = false; 129 | label.wrap = true; 130 | label.wrap_mode = Pango.WrapMode.CHAR; 131 | 132 | return label; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Widgets/Utils/DockerContainerStatusLabel.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils; 13 | 14 | namespace Widgets.Utils { 15 | class DockerContainerStatusLabel : Gtk.Label { 16 | private DockerContainerStatusLabel(DockerContainer container) { 17 | this.get_style_context ().add_class ("docker-container-status-label"); 18 | 19 | switch (container.state) { 20 | case DockerContainerState.RUNNING: 21 | this.get_style_context ().add_class ("running"); 22 | this.set_text (_ ("Running")); 23 | break; 24 | 25 | case DockerContainerState.PAUSED: 26 | this.get_style_context ().add_class ("paused"); 27 | this.set_text (_ ("Paused")); 28 | break; 29 | 30 | case DockerContainerState.STOPPED: 31 | this.get_style_context ().add_class ("stopped"); 32 | this.set_text (_ ("Stopped")); 33 | break; 34 | 35 | case DockerContainerState.UNKNOWN: 36 | this.get_style_context ().add_class ("unknown"); 37 | this.set_text (_ ("Unknown")); 38 | break; 39 | } 40 | } 41 | 42 | public static Gtk.Widget? create_by_container (DockerContainer container) { 43 | var is_running = container.state == DockerContainerState.RUNNING; 44 | var is_paused = container.state == DockerContainerState.PAUSED; 45 | 46 | if (is_running || is_paused) { 47 | return new DockerContainerStatusLabel (container); 48 | } 49 | 50 | return null; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Widgets/Utils/SettingsDialog.vala: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Whaler. 3 | 4 | Whaler is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 5 | as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 6 | Whaler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 7 | of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 8 | 9 | You should have received a copy of the GNU General Public License along with Whaler. If not, see . 10 | */ 11 | 12 | using Utils.Constants; 13 | 14 | class Widgets.Utils.SettingsDialog : Granite.Dialog { 15 | public SettingsDialog () { 16 | this.default_width = 500; 17 | this.default_height = 300; 18 | this.transient_for = Whaler.get_instance ().active_window; 19 | this.transient_for.sensitive = false; 20 | this.skip_taskbar_hint = true; 21 | this.get_style_context ().add_class ("dialog-settings"); 22 | this.add_button (_ ("Close"), Gtk.ResponseType.CANCEL); 23 | this.get_content_area ().add (this.build_content_area ()); 24 | 25 | this.response.connect ((resp_id) => { 26 | this.transient_for.sensitive = true; 27 | this.destroy (); 28 | }); 29 | 30 | this.show_all (); 31 | } 32 | 33 | private Gtk.Widget build_content_area () { 34 | var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 35 | box.expand = true; 36 | 37 | var title = new Gtk.Label (_ ("Settings")); 38 | title.get_style_context ().add_class ("h4"); 39 | title.get_style_context ().add_class ("dialog-settings-title"); 40 | 41 | box.pack_start (title, false); 42 | box.pack_end (this.build_section (), true); 43 | 44 | return box; 45 | } 46 | 47 | private Gtk.Widget build_section () { 48 | var scrolled_window = new Gtk.ScrolledWindow (null, null); 49 | 50 | var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 51 | box.get_style_context ().add_class ("dialog-settings-section"); 52 | box.pack_start (this.build_row_with_widget (_ ("API socket path:"), new SocketPathEntry ()), false); 53 | 54 | scrolled_window.add (box); 55 | 56 | return scrolled_window; 57 | } 58 | 59 | private Gtk.Widget build_row_with_widget (string label, Gtk.Widget value) { 60 | var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); 61 | 62 | box.get_style_context ().add_class ("dialog-settings-row"); 63 | box.pack_start (this.build_label (label), false); 64 | box.pack_start (value, false); 65 | 66 | return box; 67 | } 68 | 69 | private Gtk.Widget build_label (string text) { 70 | var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); 71 | box.width_request = 140; 72 | 73 | var label = new Gtk.Label (text); 74 | label.get_style_context ().add_class ("dialog-settings-row-label"); 75 | label.height_request = 27; // entry height 76 | label.halign = Gtk.Align.END; 77 | label.valign = Gtk.Align.START; 78 | 79 | box.pack_end (label, false); 80 | 81 | return box; 82 | } 83 | } 84 | 85 | class SocketPathEntry : Gtk.Box { 86 | private Gtk.Entry entry_socket_path; 87 | private Gtk.Widget notice; 88 | 89 | public SocketPathEntry () { 90 | this.get_style_context ().add_class ("docker-socket-path"); 91 | this.orientation = Gtk.Orientation.VERTICAL; 92 | this.spacing = 0; 93 | 94 | this.pack_start (this.build_entry (), false); 95 | this.pack_start (this.build_button_check (), false); 96 | } 97 | 98 | private Gtk.Widget build_entry () { 99 | var entry = new Gtk.Entry (); 100 | entry.width_request = 240; 101 | entry.placeholder_text = "/run/docker.sock"; 102 | this.entry_socket_path = entry; 103 | 104 | var settings = new Settings (APP_ID); 105 | settings.bind ("docker-api-socket-path", entry, "text", SettingsBindFlags.DEFAULT); 106 | 107 | return entry; 108 | } 109 | 110 | private Gtk.Widget build_button_check () { 111 | 112 | var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); 113 | box.get_style_context ().add_class ("button-check"); 114 | 115 | var button = new Gtk.Button.with_label (_ ("Check connection")); 116 | button.halign = Gtk.Align.START; 117 | button.clicked.connect (() => { 118 | button.sensitive = false; 119 | 120 | this.button_check_handler.begin ((_, res) => { 121 | this.button_check_handler.end (res); 122 | button.sensitive = true; 123 | }); 124 | }); 125 | box.pack_start (button, false); 126 | 127 | return box; 128 | } 129 | 130 | private Gtk.Widget build_notice (string message, bool successful) { 131 | var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 132 | box.get_style_context ().add_class ("notice"); 133 | box.get_style_context ().add_class (successful ? "successfull" : "failed"); 134 | 135 | var title = new Gtk.Label (successful ? _ ("Success") : _ ("Error")); 136 | title.get_style_context ().add_class ("h4"); 137 | title.halign = Gtk.Align.START; 138 | box.pack_start (title, false); 139 | 140 | var msg = new Gtk.Label (message); 141 | msg.halign = Gtk.Align.START; 142 | msg.wrap = true; 143 | 144 | box.pack_end (msg, false); 145 | 146 | return box; 147 | } 148 | 149 | private async void button_check_handler () { 150 | var settings = new Settings (APP_ID); 151 | var state = State.Root.get_instance (); 152 | var api_client = new Docker.ApiClient (); 153 | var err_msg = _ ("Incorrect socket path \"%s\""); 154 | 155 | if (this.notice != null) { 156 | this.remove (this.notice); 157 | } 158 | 159 | try { 160 | // 161 | api_client.http_client.unix_socket_path = this.entry_socket_path.text; 162 | yield api_client.ping(); 163 | 164 | // 165 | var engine = settings.get_string ("docker-api-socket-path").contains ("podman.sock") 166 | ? "Podman" 167 | : "Docker"; 168 | 169 | var docker_version_info = yield api_client.version (); 170 | this.notice = this.build_notice ( 171 | @"$engine v$(docker_version_info.version), API v$(docker_version_info.api_version)", 172 | true 173 | ); 174 | 175 | yield state.containers_load (); 176 | state.active_screen = Widgets.ScreenMain.CODE; 177 | } catch (Docker.ApiClientError error) { 178 | this.notice = this.build_notice (err_msg.printf (this.entry_socket_path.text), false); 179 | } finally { 180 | this.pack_start (this.notice, false); 181 | this.show_all (); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | configuration = configuration_data() 2 | 3 | configuration.set_quoted('APP_ID', meson.project_name()) 4 | configuration.set_quoted('APP_VERSION', meson.project_version()) 5 | configuration.set_quoted('LOCALE_DIR', join_paths (get_option('prefix'), get_option('localedir'))) 6 | 7 | constants = configure_file( 8 | input : 'Utils/Constants.vala.in', 9 | output : 'Constants.vala', 10 | configuration : configuration 11 | ) 12 | 13 | # 14 | sources = files( 15 | 'Application.vala', 16 | 17 | 'Utils/DockerContainer.vala', 18 | 'Utils/HttpClient.vala', 19 | 'Utils/Helpers.vala', 20 | 'Utils/Theme.vala', 21 | 'Utils/Sorting/SortingInterface.vala', 22 | 'Utils/Sorting/SortingName.vala', 23 | 'Utils/Sorting/SortingType.vala', 24 | 'Utils/Sorting/SortingStatus.vala', 25 | 26 | 'State/Root.vala', 27 | 'State/ScreenMain.vala', 28 | 'State/ScreenDockerContainer.vala', 29 | 30 | 'Widgets/HeaderBar.vala', 31 | 'Widgets/ScreenManager.vala', 32 | 'Widgets/ScreenError.vala', 33 | 'Widgets/ScreenMain.vala', 34 | 'Widgets/ScreenDockerContainer.vala', 35 | 36 | 'Widgets/Screens/Main/ContainersGrid.vala', 37 | 'Widgets/Screens/Main/ContainersGridFilter.vala', 38 | 'Widgets/Screens/Main/ContainerCard.vala', 39 | 'Widgets/Screens/Main/ContainerCardActions.vala', 40 | 41 | 'Widgets/Screens/DockerContainer/TopBar.vala', 42 | 'Widgets/Screens/DockerContainer/TopBarActions.vala', 43 | 'Widgets/Screens/DockerContainer/SideBar.vala', 44 | 'Widgets/Screens/DockerContainer/SideBarItem.vala', 45 | 'Widgets/Screens/DockerContainer/SideBarSeparator.vala', 46 | 'Widgets/Screens/DockerContainer/Log.vala', 47 | 'Widgets/Screens/DockerContainer/LogOutput.vala', 48 | 49 | 'Widgets/Utils/SettingsDialog.vala', 50 | 'Widgets/Utils/ConfirmationDialog.vala', 51 | 'Widgets/Utils/ContainerInfoDialog.vala', 52 | 'Widgets/Utils/DockerContainerStatusLabel.vala', 53 | 54 | 'Docker/ApiClient.vala', 55 | 'Docker/ContainerLogWatcher.vala', 56 | ) -------------------------------------------------------------------------------- /vapi/libcurl.deps: -------------------------------------------------------------------------------- 1 | posix 2 | --------------------------------------------------------------------------------