mirror of
https://github.com/frosch95/K8sFileBrowser.git
synced 2026-04-11 21:08:22 +02:00
Compare commits
8 Commits
v.0.0.3-al
...
v0.0.6-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| ba39661926 | |||
| 59e9131fed | |||
| d544f75c99 | |||
| ec60e55c7f | |||
| a4fb00010e | |||
|
|
6ad58270a9 | ||
|
|
e284e3f532 | ||
| 7e3c4248e1 |
@@ -28,6 +28,8 @@
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.1" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.1" />
|
||||
<PackageReference Include="Avalonia.Xaml.Interactions" Version="11.0.2" />
|
||||
<PackageReference Include="Avalonia.Xaml.Interactivity" Version="11.0.2" />
|
||||
<PackageReference Include="KubernetesClient" Version="11.0.44" />
|
||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
|
||||
23
K8sFileBrowser/Models/Container.cs
Normal file
23
K8sFileBrowser/Models/Container.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace K8sFileBrowser.Models;
|
||||
|
||||
public class Container
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is Container container)
|
||||
{
|
||||
return Name == container.Name;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => Name.GetHashCode();
|
||||
}
|
||||
@@ -6,6 +6,7 @@ public class FileInformation
|
||||
{
|
||||
public string Parent { get; set; } = string.Empty;
|
||||
public FileType Type { get; set; } = FileType.File;
|
||||
public string DisplayName => Parent.Length < 2 ? Name[Parent.Length..] : Name[( Parent.Length + 1)..];
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Size { get; set; } = string.Empty;
|
||||
public DateTimeOffset Date { get; set; } = DateTimeOffset.MinValue;
|
||||
|
||||
@@ -15,9 +15,9 @@ public interface IKubernetesService
|
||||
Task<IEnumerable<Namespace>> GetNamespacesAsync();
|
||||
Task<IEnumerable<Pod>> GetPodsAsync(string namespaceName, CancellationToken cancellationToken = default);
|
||||
IList<FileInformation> GetFiles(string namespaceName, string podName, string containerName, string path);
|
||||
Task DownloadFile(Namespace? selectedNamespace, Pod? selectedPod, FileInformation selectedFile,
|
||||
Task DownloadFile(Namespace? selectedNamespace, Pod? selectedPod, Container? selectedContainer, FileInformation selectedFile,
|
||||
string? saveFileName, CancellationToken cancellationToken = default);
|
||||
Task DownloadLog(Namespace? selectedNamespace, Pod? selectedPod,
|
||||
Task DownloadLog(Namespace? selectedNamespace, Pod? selectedPod, Container? selectedContainer,
|
||||
string? saveFileName, CancellationToken cancellationToken = default);
|
||||
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public class KubernetesService : IKubernetesService
|
||||
_kubernetesClient = kubernetesClient;
|
||||
}
|
||||
|
||||
public async Task DownloadFile(Namespace? selectedNamespace, Pod? selectedPod, FileInformation selectedFile,
|
||||
public async Task DownloadFile(Namespace? selectedNamespace, Pod? selectedPod, Container? selectedContainer, FileInformation selectedFile,
|
||||
string? saveFileName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Information("{SelectedNamespace} - {SelectedPod} - {@SelectedFile} - {SaveFileName}",
|
||||
@@ -139,14 +139,14 @@ public class KubernetesService : IKubernetesService
|
||||
await _kubernetesClient.NamespacedPodExecAsync(
|
||||
selectedPod?.Name,
|
||||
selectedNamespace?.Name,
|
||||
selectedPod?.Containers.First(),
|
||||
selectedContainer?.Name,
|
||||
new[] { "sh", "-c", $"tar cf - {selectedFile.Name}" },
|
||||
false,
|
||||
handler,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task DownloadLog(Namespace? selectedNamespace, Pod? selectedPod,
|
||||
public async Task DownloadLog(Namespace? selectedNamespace, Pod? selectedPod, Container? selectedContainer,
|
||||
string? saveFileName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Log.Information("{SelectedNamespace} - {SelectedPod} - {SaveFileName}",
|
||||
@@ -155,7 +155,7 @@ public class KubernetesService : IKubernetesService
|
||||
var response = await _kubernetesClient.CoreV1.ReadNamespacedPodLogWithHttpMessagesAsync(
|
||||
selectedPod?.Name,
|
||||
selectedNamespace?.Name,
|
||||
container: selectedPod?.Containers.First(),
|
||||
container: selectedContainer?.Name,
|
||||
follow: false , cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using K8sFileBrowser.Models;
|
||||
@@ -12,7 +13,7 @@ namespace K8sFileBrowser.ViewModels;
|
||||
|
||||
public class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private ObservableAsPropertyHelper<IEnumerable<ClusterContext>> _clusterContexts;
|
||||
private ObservableAsPropertyHelper<IEnumerable<ClusterContext>> _clusterContexts = null!;
|
||||
public IEnumerable<ClusterContext> ClusterContexts => _clusterContexts.Value;
|
||||
|
||||
private ClusterContext? _selectedClusterContext;
|
||||
@@ -23,7 +24,7 @@ public class MainWindowViewModel : ViewModelBase
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedClusterContext, value);
|
||||
}
|
||||
|
||||
private IEnumerable<Namespace> _namespaces;
|
||||
private IEnumerable<Namespace> _namespaces = null!;
|
||||
public IEnumerable<Namespace> Namespaces
|
||||
{
|
||||
get => _namespaces;
|
||||
@@ -38,7 +39,7 @@ public class MainWindowViewModel : ViewModelBase
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedNamespace, value);
|
||||
}
|
||||
|
||||
private ObservableAsPropertyHelper<IEnumerable<Pod>> _pods;
|
||||
private ObservableAsPropertyHelper<IEnumerable<Pod>> _pods = null!;
|
||||
public IEnumerable<Pod> Pods => _pods.Value;
|
||||
|
||||
private Pod? _selectedPod;
|
||||
@@ -48,8 +49,23 @@ public class MainWindowViewModel : ViewModelBase
|
||||
get => _selectedPod;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedPod, value);
|
||||
}
|
||||
|
||||
private IEnumerable<Container>? _containers;
|
||||
public IEnumerable<Container>? Containers
|
||||
{
|
||||
get => _containers;
|
||||
set => this.RaiseAndSetIfChanged(ref _containers, value);
|
||||
}
|
||||
|
||||
private ObservableAsPropertyHelper<IEnumerable<FileInformation>> _fileInformation;
|
||||
private Container? _selectedContainer;
|
||||
|
||||
public Container? SelectedContainer
|
||||
{
|
||||
get => _selectedContainer;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedContainer, value);
|
||||
}
|
||||
|
||||
private ObservableAsPropertyHelper<IEnumerable<FileInformation>> _fileInformation = null!;
|
||||
public IEnumerable<FileInformation> FileInformation => _fileInformation.Value;
|
||||
|
||||
private FileInformation? _selectedFile;
|
||||
@@ -68,19 +84,19 @@ public class MainWindowViewModel : ViewModelBase
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedPath, value);
|
||||
}
|
||||
|
||||
private Message _message;
|
||||
private Message _message = null!;
|
||||
public Message Message
|
||||
{
|
||||
get => _message;
|
||||
set => this.RaiseAndSetIfChanged(ref _message, value);
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> DownloadCommand { get; private set; }
|
||||
public ReactiveCommand<Unit, Unit> DownloadLogCommand { get; private set; }
|
||||
public ReactiveCommand<Unit, Unit> ParentCommand { get; private set; }
|
||||
public ReactiveCommand<Unit, Unit> OpenCommand { get; private set; }
|
||||
public ReactiveCommand<Unit, Unit> DownloadCommand { get; private set; } = null!;
|
||||
public ReactiveCommand<Unit, Unit> DownloadLogCommand { get; private set; } = null!;
|
||||
public ReactiveCommand<Unit, Unit> ParentCommand { get; private set; } = null!;
|
||||
public ReactiveCommand<Unit, Unit> OpenCommand { get; private set; } = null!;
|
||||
|
||||
private ReactiveCommand<Namespace, IEnumerable<Pod>> GetPodsForNamespace { get; set; }
|
||||
private ReactiveCommand<Namespace, IEnumerable<Pod>> GetPodsForNamespace { get; set; } = null!;
|
||||
|
||||
|
||||
public MainWindowViewModel()
|
||||
@@ -98,6 +114,7 @@ public class MainWindowViewModel : ViewModelBase
|
||||
// register the listeners
|
||||
RegisterReadNamespaces(kubernetesService);
|
||||
RegisterReadPods();
|
||||
RegisterReadContainers();
|
||||
RegisterReadFiles(kubernetesService);
|
||||
RegisterResetPath();
|
||||
|
||||
@@ -123,18 +140,37 @@ public class MainWindowViewModel : ViewModelBase
|
||||
// reset the path when the pod or namespace changes
|
||||
this.WhenAnyValue(c => c.SelectedPod, c => c.SelectedNamespace)
|
||||
.Throttle(new TimeSpan(10))
|
||||
.ObserveOn(RxApp.TaskpoolScheduler)
|
||||
.Subscribe(x => SelectedPath = "/");
|
||||
}
|
||||
|
||||
private void RegisterReadContainers()
|
||||
{
|
||||
// read the file information when the path changes
|
||||
this
|
||||
.WhenAnyValue(c => c.SelectedPod, c => c.SelectedNamespace)
|
||||
.Throttle(new TimeSpan(10))
|
||||
.Select(x => x.Item2 == null || x.Item1 == null
|
||||
? new List<Container>()
|
||||
: x.Item1.Containers.Select(c => new Container {Name = c}))
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe( x => Containers = x);
|
||||
|
||||
this.WhenAnyValue(x => x.Containers)
|
||||
.Throttle(new TimeSpan(10))
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(x => SelectedContainer = x?.FirstOrDefault());
|
||||
}
|
||||
|
||||
private void RegisterReadFiles(IKubernetesService kubernetesService)
|
||||
{
|
||||
// read the file information when the path changes
|
||||
_fileInformation = this
|
||||
.WhenAnyValue(c => c.SelectedPath, c => c.SelectedPod, c => c.SelectedNamespace)
|
||||
.WhenAnyValue(c => c.SelectedPath, c => c.SelectedPod, c => c.SelectedNamespace, c => c.SelectedContainer)
|
||||
.Throttle(new TimeSpan(10))
|
||||
.Select(x => x.Item3 == null || x.Item2 == null
|
||||
.Select(x => x.Item3 == null || x.Item2 == null || x.Item1 == null || x.Item4 == null
|
||||
? new List<FileInformation>()
|
||||
: kubernetesService.GetFiles(x.Item3!.Name, x.Item2!.Name, x.Item2!.Containers.First(),
|
||||
: kubernetesService.GetFiles(x.Item3!.Name, x.Item2!.Name, x.Item4!.Name,
|
||||
x.Item1))
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.ToProperty(this, x => x.FileInformation);
|
||||
@@ -167,7 +203,7 @@ public class MainWindowViewModel : ViewModelBase
|
||||
GetPodsForNamespace = ReactiveCommand.CreateFromObservable<Namespace, IEnumerable<Pod>>(ns =>
|
||||
Observable.StartAsync(_ => PodsAsync(ns, kubernetesService), RxApp.TaskpoolScheduler));
|
||||
|
||||
GetPodsForNamespace.ThrownExceptions
|
||||
GetPodsForNamespace.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult());
|
||||
}
|
||||
|
||||
@@ -186,7 +222,7 @@ public class MainWindowViewModel : ViewModelBase
|
||||
}
|
||||
}, isNotRoot, RxApp.MainThreadScheduler);
|
||||
|
||||
ParentCommand.ThrownExceptions
|
||||
ParentCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult());
|
||||
}
|
||||
|
||||
@@ -205,13 +241,13 @@ public class MainWindowViewModel : ViewModelBase
|
||||
if (saveFileName != null)
|
||||
{
|
||||
ShowWorkingMessage("Downloading Log...");
|
||||
await kubernetesService.DownloadLog(SelectedNamespace, SelectedPod, saveFileName);
|
||||
await kubernetesService.DownloadLog(SelectedNamespace, SelectedPod, SelectedContainer, saveFileName);
|
||||
HideWorkingMessage();
|
||||
}
|
||||
}, RxApp.TaskpoolScheduler);
|
||||
}, isSelectedPod, RxApp.MainThreadScheduler);
|
||||
|
||||
DownloadLogCommand.ThrownExceptions
|
||||
DownloadLogCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult());
|
||||
}
|
||||
|
||||
@@ -231,13 +267,13 @@ public class MainWindowViewModel : ViewModelBase
|
||||
if (saveFileName != null)
|
||||
{
|
||||
ShowWorkingMessage("Downloading File...");
|
||||
await kubernetesService.DownloadFile(SelectedNamespace, SelectedPod, SelectedFile, saveFileName);
|
||||
await kubernetesService.DownloadFile(SelectedNamespace, SelectedPod, SelectedContainer, SelectedFile, saveFileName);
|
||||
HideWorkingMessage();
|
||||
}
|
||||
}, RxApp.TaskpoolScheduler);
|
||||
}, isFile, RxApp.MainThreadScheduler);
|
||||
|
||||
DownloadCommand.ThrownExceptions
|
||||
DownloadCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult());
|
||||
}
|
||||
|
||||
@@ -250,7 +286,7 @@ public class MainWindowViewModel : ViewModelBase
|
||||
OpenCommand = ReactiveCommand.Create(() => { SelectedPath = SelectedFile != null ? SelectedFile!.Name : "/"; },
|
||||
isDirectory, RxApp.MainThreadScheduler);
|
||||
|
||||
OpenCommand.ThrownExceptions
|
||||
OpenCommand.ThrownExceptions.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Subscribe(ex => ShowErrorMessage(ex.Message).ConfigureAwait(false).GetAwaiter().GetResult());
|
||||
}
|
||||
|
||||
@@ -277,8 +313,10 @@ public class MainWindowViewModel : ViewModelBase
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ShowErrorMessage(e.Message);
|
||||
RxApp.MainThreadScheduler.Schedule(Action);
|
||||
return new List<Namespace>();
|
||||
|
||||
async void Action() => await ShowErrorMessage(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,12 +72,24 @@
|
||||
</ListBox>
|
||||
<GridSplitter Grid.Column="1" ResizeDirection="Columns" />
|
||||
<Grid Grid.Column="2" RowDefinitions="Auto, *">
|
||||
<Grid ColumnDefinitions="*, Auto">
|
||||
<Grid ColumnDefinitions="*, Auto, Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<TextBlock Text="Current Directory" VerticalAlignment="Center" Margin="10 0 0 0"></TextBlock>
|
||||
<TextBlock Text="{Binding SelectedPath}" VerticalAlignment="Center" Margin="10 0 0 0"></TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="4" Margin="10" HorizontalAlignment="Right">
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<Label VerticalAlignment="Center"
|
||||
Margin="0 0 10 0">
|
||||
Container:
|
||||
</Label>
|
||||
<ComboBox ItemsSource="{Binding Containers}"
|
||||
SelectedItem="{Binding SelectedContainer}"
|
||||
VerticalAlignment="Center"
|
||||
MinWidth="200"
|
||||
Margin="0 0 10 0">
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="4" Margin="10" HorizontalAlignment="Right">
|
||||
|
||||
<Button Command="{Binding ParentCommand}" VerticalAlignment="Center" ToolTip.Tip="Go To Parent Directory">
|
||||
<PathIcon Data="{StaticResource arrow_curve_up_left_regular}"></PathIcon>
|
||||
@@ -102,7 +114,7 @@
|
||||
BorderThickness="1"
|
||||
SelectionMode="Single"
|
||||
SelectedItem="{Binding SelectedFile}"
|
||||
>
|
||||
Focusable="False">
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridColumnHeader">
|
||||
<Setter Property="FontSize" Value="14"></Setter>
|
||||
@@ -116,19 +128,63 @@
|
||||
<DataGridTemplateColumn Header="Type">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate DataType="models:FileInformation">
|
||||
<Border ToolTip.Tip="{Binding Type}" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<StackPanel>
|
||||
<Border ToolTip.Tip="{Binding Type}" Background="Transparent">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<PathIcon Data="{StaticResource folder_regular}" IsVisible="{Binding IsDirectory}"></PathIcon>
|
||||
<PathIcon Data="{StaticResource document_regular}" IsVisible="{Binding IsFile}"></PathIcon>
|
||||
<PathIcon Data="{StaticResource document_unknown_regular}" IsVisible="{Binding IsUnknown}"></PathIcon>
|
||||
</StackPanel>
|
||||
<Interaction.Behaviors>
|
||||
<EventTriggerBehavior EventName="DoubleTapped">
|
||||
<InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window }}" />
|
||||
</EventTriggerBehavior>
|
||||
</Interaction.Behaviors>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="Name" Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate DataType="models:FileInformation">
|
||||
<Border Background="Transparent">
|
||||
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center"/>
|
||||
<Interaction.Behaviors>
|
||||
<EventTriggerBehavior EventName="DoubleTapped">
|
||||
<InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window }}" />
|
||||
</EventTriggerBehavior>
|
||||
</Interaction.Behaviors>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="Size" Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate DataType="models:FileInformation">
|
||||
<Border Background="Transparent">
|
||||
<TextBlock Text="{Binding Size}" VerticalAlignment="Center"/>
|
||||
<Interaction.Behaviors>
|
||||
<EventTriggerBehavior EventName="DoubleTapped">
|
||||
<InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window }}" />
|
||||
</EventTriggerBehavior>
|
||||
</Interaction.Behaviors>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="Date" Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate DataType="models:FileInformation">
|
||||
<Border Background="Transparent">
|
||||
<TextBlock Text="{Binding Date}" VerticalAlignment="Center"/>
|
||||
<Interaction.Behaviors>
|
||||
<EventTriggerBehavior EventName="DoubleTapped">
|
||||
<InvokeCommandAction Command="{Binding ((vm:MainWindowViewModel)DataContext).OpenCommand, RelativeSource={RelativeSource AncestorType=Window }}" />
|
||||
</EventTriggerBehavior>
|
||||
</Interaction.Behaviors>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Header="Name" Width="*" Binding="{Binding Name}" />
|
||||
<DataGridTextColumn Header="Size" Binding="{Binding Size}" />
|
||||
<DataGridTextColumn Header="Date" Binding="{Binding Date}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Andreas Billmann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Reference in New Issue
Block a user