CSS Flex Cheat Sheet

display:flex

Hauptachse festlegenflex-directionrow (d), row-reverse, column, column-reverse
Items auf Hauptachse anordnenjustify-contentflex-start (d), flex-end, center, space-between, space-around, space-evenly
Items auf Querachse andordnen
(Space/Verteilung: gleich)
align-itemsflex-start, flex-end, center, baseline, stretch (d)
Items auf Querarchse spacenalign-contentflex-start, flex-end, center, space-between, space-around, space-evenly, stretch (d)
Umbruch auf Hauptachseflex-wrapnowrap (d), wrap, wrap-reverse
Zelle(n) auf Querachse anordnen (anstatt Hauptachse)align-selfflex-start, flex-end, center, baseline, stretch
Hauptachse + Umbruch dortflex-flowflex-direction flex-wrap
Startgröße einer Zelleflex-basisGröße, auto
Anteilsmäßige Veränderungflex-grow
flex-shrink
0-1
Reihenfolge der Items ändernorderAnzahl Positionen links (-) oder rechts

https://flexboxfroggy.com

USB Power Save verhindern

Wenn ein Gerät (Kartenleser in meinem Fall) nicht ausgeschaltet werden soll kann man das entsprechende Hakerl im Device Manager setzen (“Allow the computer to turn off this device to save power”) oder wenn mans automatisiert machen will diesen Registrywert setzen:

HKLM/SYSTEM/CurrentControlSet/Enum/USB/VID_xxxx&PID_yyyy/Device Parameters/WDF/IdleInWorkingState = 0 (REG_DWORD)

xxxx/yyyy naturgemäß durch die Werte vom jeweiligen Gerät ersetzen. Nach Reboot ist das Hakerl im GUI weg und das Gerät auf Always On.

Tailwind CSS Intellisense mit Code in Giraffe Projekt

Die Tailwind CSS Intellisense Extension für VS Code kann nicht mit der F# Syntax für die HTML Objekte bei der Defaultmethode von Giraffe umgehen – dazu muss man diese Settings in die .config einfügen:

    "tailwindCSS.includeLanguages": {
        "fsharp": "html"
    },
   "tailwindCSS.experimental.classRegex": [
       "_class\\s+\"([^\"]*)\""
    ]

Linux Citrix Workspace SSL Fehler

Wenn die Linux Citrix Workspace App wieder mal die Zertifikate von StoreFront, Delivery Controller, Netscaler oder was weiß ich nicht kennt hilft (vielleicht) das:

sudo ln -s /usr/share/ca-certificates/mozilla/* /opt/Citrix/ICAClient/keystore/cacertsa

O365 Lizenzstatus via (Cloud)PowerShell ermitteln

Wenn man auf die schnelle rausfinden muss ob ein bestimmter Benutzer eine O365 Lizenz hat und MS das Portal wieder mal umgebaut hat ist man mit PS (oder Cloud PS wenn man Portal eh schon offen hat) deutlich schneller:

Connect-AzureAD

# über UPN
Get-AzureADUser -Filter "userPrincipalName eq 'whoever@whereever.com'"|Get-AzureADUserLicenseDetail|fl *

# über Name
Get-AzureADUser -Filter "startswith(DisplayName,'Who Ever')"|Get-AzureADUserLicenseDetail|fl *

SAPPing – simpler SAP .NET Connector Client mit SNC Logon

Weil ich das für einen SAP Call mit DirectAccess (ja, der SAP .NET Connector verhält sich anders als das was in SAP GUI/NWBC eingebaut ist in diesem Umfeld) die Verbindung nachprogrammiert habe und vielleicht jemand das auch brauchen könnte – im Projekt ist nur SAPNCO.DLL und SAPNCO_UTILS.DLL von SAP zu referenzieren.

Dreist von codeproject geklaut und angepasst (SNC)/vereinfacht – außerdem kann man hier den Text kopieren und hat nicht nur Screenshots 😀

using System;
using SAP.Middleware.Connector;

namespace SAPPing
{
   public class SAPDestinationConfig:IDestinationConfiguration
   {
      public bool ChangeEventsSupported()
      {
         return false;
      }

      public event RfcDestinationManager.ConfigurationChangeHandler ConfigurationChanged;

      public RfcConfigParameters GetParameters(string destinationName)
      {
         RfcConfigParameters par=new RfcConfigParameters();
         par.Add(RfcConfigParameters.Name, "ABC");
         // Direktverbindung (AppServerHost) oder via MessageServer (=LoadBalanced)
         par.Add(RfcConfigParameters.AppServerHost, "abcsaphost.mydomain.com");
         //par.Add(RfcConfigParameters.MessageServerHost, "abcsaphost.mydomain.com");
         //par.Add(RfcConfigParameters.MessageServerService, "1234");  // port vonLandscape XML
         par.Add(RfcConfigParameters.SystemNumber, "11");
         par.Add(RfcConfigParameters.SystemID, "ABC");
         par.Add(RfcConfigParameters.Client, "999");   // "Mandant"
         par.Add(RfcConfigParameters.Language, "DE");
         par.Add(RfcConfigParameters.PoolSize, "10");
         par.Add(RfcConfigParameters.SncPartnerName, "p:CN=whatever/abc@mydomain.com");   // von Landscape XML
         par.Add(RfcConfigParameters.SncMyName, "...Rechtsklick in SecureLogin und 'Copy SNC name to clipboard' - auch irgendwas mit 'p:CN=user@mydomain.com' bei Kerberos");
         par.Add(RfcConfigParameters.SncMode, "1");    // SNC benutzen
         par.Add(RfcConfigParameters.SncQOP, "9");     // "beste" SNC Methode automatisch wählen
         par.Add(RfcConfigParameters.Trace, "4");      // volles Tracing

         return par;
      }
   }

   class Program
   {
      static void Main(string[] args)
      {
         RfcTrace.TraceDirectory = @"C:\TEMP\SAP";
         RfcTrace.DefaultTraceLevel = (uint)RfcTracing.Level4;
         GeneralConfiguration.CPICTraceLevel = 3;

         IDestinationConfiguration config = new SAPDestinationConfig();
         config.GetParameters("ABC");
         if (RfcDestinationManager.TryGetDestination("ABC") == null)
         {
            RfcDestinationManager.RegisterDestinationConfiguration(config);
         }

         try
         {
            RfcDestination dest = RfcDestinationManager.GetDestination("ABC");
            dest.Ping();
            Console.WriteLine($"SAP ping OK, connected to {dest.SystemAttributes.PartnerHost} running {dest.SystemAttributes.PartnerRelease}");
         }
         catch (Exception oExc)
         {
            Console.WriteLine(oExc.ToString());
         }
      }
   }
}

Kiosk App mit WebView2 in F#

Auch mit Visual Studio 2022 ist F# noch immer ein Stiefkind was WinForms Entwicklung angeht, trotzdem sind Dinge wie gehabt wesentlich knapper, präziser und je nach Geschmack schöner mit F# abzubilden – im speziellen eine sehr einfache Kiosk App die nichts anderes als eine Seite Anzeigen soll von der man am besten nicht “entkommt”:

  • Kein Kontextmenü
  • Keine Downloads bzw. nur für Autostart (bestimmte Dateiextension)
  • Keine neuen Fenster
  • Nur bestimmte URLs (Regex) erlaubt

Ergebnis:

open System
open System.Windows.Forms
open System.IO
open System.Text.RegularExpressions
open System.Diagnostics
open Microsoft.Web.WebView2.WinForms
open Microsoft.Web.WebView2.Core

let InitForm (env:CoreWebView2Environment) =
   let webViewForm = new Form(Text = "Kiosk Titel", Width=int (float Screen.PrimaryScreen.Bounds.Width * 0.8), Height=int (float Screen.PrimaryScreen.Bounds.Height * 0.6), StartPosition=FormStartPosition.CenterScreen, ShowIcon=false)

   let webView = new WebView2()
   webView.Dock <- System.Windows.Forms.DockStyle.Fill
   webView.Location <- new System.Drawing.Point(0, 0)
   webViewForm.Controls.Add webView
   webView.EnsureCoreWebView2Async(env) |> ignore
   webView.CoreWebView2InitializationCompleted.AddHandler(fun sender args ->
      webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled <- false
      webView.CoreWebView2.Settings.AreDevToolsEnabled <- false
      webView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled <- false
      webView.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled <- false
      webView.CoreWebView2.Settings.AreHostObjectsAllowed <- false
      webView.CoreWebView2.Settings.IsPasswordAutosaveEnabled <- false
      webView.CoreWebView2.Settings.IsGeneralAutofillEnabled <- false
      webView.CoreWebView2.Settings.IsWebMessageEnabled <- false
     
      webView.CoreWebView2.DownloadStarting.AddHandler(fun downloadsender downloadargs ->
         let deferral = downloadargs.GetDeferral()
         System.Threading.SynchronizationContext.Current.Post(fun _ ->
            downloadargs.Handled <- true     // no download dialog
            downloadargs.DownloadOperation.StateChanged.AddHandler(fun statesender stateargs -> 
               if (downloadargs.DownloadOperation.State = CoreWebView2DownloadState.Completed) then
                  if (System.IO.Path.GetExtension(downloadargs.DownloadOperation.ResultFilePath).ToUpper() = ".XXX") then // valid extensions to start
                     let proc=new ProcessStartInfo(downloadargs.DownloadOperation.ResultFilePath)
                     proc.UseShellExecute <- true
                     Process.Start(proc) |> ignore
            )
            deferral.Dispose()
         , null)
      )

      webView.CoreWebView2.NavigationStarting.AddHandler(fun navigatesender navigateargs ->
         if (Regex.Match(navigateargs.Uri,"^https://(mysite|myothersite)\.local/.*", RegexOptions.IgnoreCase).Success = false) then navigateargs.Cancel <- true
      )

      webView.CoreWebView2.NewWindowRequested.AddHandler(fun newwindowsender newwindowargs ->
         newwindowargs.Handled <- true      // do not open window
      )
      webView.CoreWebView2.Navigate "https://mysite.local" |> ignore
   )
   webViewForm


[<EntryPoint; STAThread>]
let main argv = 
   let userDataFolder = $"{Path.GetTempPath()}\KioskWebUserData"
   let options = new CoreWebView2EnvironmentOptions("--kiosk")
   let env=CoreWebView2Environment.CreateAsync("",userDataFolder,options).GetAwaiter().GetResult()

   Application.EnableVisualStyles()
   Application.SetCompatibleTextRenderingDefault(false)
   Application.Run(InitForm env)
   0

Projektdatei:

  
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Program.fs" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1020.30" />
  </ItemGroup>

</Project>